diff --git a/cmd/runtimetest/main.go b/cmd/runtimetest/main.go index 324cef8d7..6d9c8d9b1 100644 --- a/cmd/runtimetest/main.go +++ b/cmd/runtimetest/main.go @@ -314,7 +314,7 @@ func validateRootFS(spec *rspec.Spec) error { if spec.Root.Readonly { err := testWriteAccess("/") if err == nil { - return specerror.NewError(specerror.ReadonlyFilesystem, fmt.Errorf("rootfs must be readonly"), rspec.Version) + return specerror.NewError(specerror.RootReadonlyImplement, fmt.Errorf("rootfs must be readonly"), rspec.Version) } } diff --git a/specerror/error.go b/specerror/error.go index c75bb6b14..4505831ba 100644 --- a/specerror/error.go +++ b/specerror/error.go @@ -26,23 +26,90 @@ const ( // 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 + // SpecVersionInSemVer represents "* **`ociVersion`** (string, Required) MUST be in [SemVer v2.0.0][semver-v2.0.0] format and specifies the version of the Open Container Runtime Specification with which the bundle complies." + SpecVersionInSemVer + // RootOnWindowsRequired represents "On Windows, for Windows Server Containers, this field is REQUIRED." + RootOnWindowsRequired + // RootOnHyperVNotSet represents "For [Hyper-V Containers](config-windows.md#hyperv), this field MUST NOT be set." + RootOnHyperVNotSet + // RootOnNonHyperVRequired represents "On all other platforms, this field is REQUIRED." + RootOnNonHyperVRequired + // RootPathOnWindowsGUID represents "* On Windows, `path` MUST be a [volume GUID path][naming-a-volume]." + RootPathOnWindowsGUID + // RootPathOnPosixConvention represents "The value SHOULD be the conventional `rootfs`." + RootPathOnPosixConvention + // RootPathExist represents "A directory MUST exist at the path declared by the field." + RootPathExist + // RootReadonlyImplement represents "* **`readonly`** (bool, OPTIONAL) If true then the root filesystem MUST be read-only inside the container, defaults to false." + RootReadonlyImplement + // RootReadonlyOnWindowsFalse represents "* On Windows, this field MUST be omitted or false." + RootReadonlyOnWindowsFalse + // MountsInOrder represents "The runtime MUST mount entries in the listed order." + MountsInOrder + // MountsDestAbs represents "This value MUST be an absolute path." + MountsDestAbs + // MountsDestOnWindowsNotNested represents "* Windows: one mount destination MUST NOT be nested within another mount (e.g., c:\\foo and c:\\foo\\bar)." + MountsDestOnWindowsNotNested + // MountsOptionsOnWindowsROSupport represents "* Windows: runtimes MUST support `ro`, mounting the filesystem read-only when `ro` is given." + MountsOptionsOnWindowsROSupport + // ProcRequiredAtStart represents "This property is REQUIRED when [`start`](runtime.md#start) is called." + ProcRequiredAtStart + // ProcConsoleSizeIgnore represents "Runtimes MUST ignore `consoleSize` if `terminal` is `false` or unset." + ProcConsoleSizeIgnore + // ProcCwdAbs represents "This value MUST be an absolute path." + ProcCwdAbs + // ProcArgsOneEntryRequired represents "This specification extends the IEEE standard in that at least one entry is REQUIRED, and that entry is used with the same semantics as `execvp`'s *file*." + ProcArgsOneEntryRequired + // PosixProcRlimitsTypeError represents "The runtime MUST [generate an error](runtime.md#errors) for any values which cannot be mapped to a relevant kernel interface" + PosixProcRlimitsTypeError + // PosixProcRlimitsTypeGet represents "For each entry in `rlimits`, a [`getrlimit(3)`][getrlimit.3] on `type` MUST succeed." + PosixProcRlimitsTypeGet + // PosixProcRlimitsSoftMatchCur represents "`rlim.rlim_cur` MUST match the configured value." + PosixProcRlimitsSoftMatchCur + // PosixProcRlimitsHardMatchMax represents "`rlim.rlim_max` MUST match the configured value." + PosixProcRlimitsHardMatchMax + // PosixProcRlimitsErrorOnDup represents "If `rlimits` contains duplicated entries with same `type`, the runtime MUST [generate an error](runtime.md#errors)." + PosixProcRlimitsErrorOnDup + // LinuxProcCapError represents "Any value which cannot be mapped to a relevant kernel interface MUST cause an error." + LinuxProcCapError + // LinuxProcOomScoreAdjSet represents "If `oomScoreAdj` is set, the runtime MUST set `oom_score_adj` to the given value." + LinuxProcOomScoreAdjSet + // LinuxProcOomScoreAdjNotSet represents "If `oomScoreAdj` is not set, the runtime MUST NOT change the value of `oom_score_adj`." + LinuxProcOomScoreAdjNotSet + // PlatformSpecConfOnWindowsSet represents "This MUST be set if the target platform of this spec is `windows`." + PlatformSpecConfOnWindowsSet + // PosixHooksPathAbs represents "This specification extends the IEEE standard in that **`path`** MUST be absolute." + PosixHooksPathAbs + // PosixHooksTimeoutPositive represents "If set, `timeout` MUST be greater than zero." + PosixHooksTimeoutPositive + // PosixHooksCalledInOrder represents "Hooks MUST be called in the listed order." + PosixHooksCalledInOrder + // PosixHooksStateToStdin represents "The [state](runtime.md#state) of the container MUST be passed to hooks over stdin so that they may do work appropriate to the current state of the container." + PosixHooksStateToStdin + // PrestartTiming represents "The pre-start hooks MUST be called after the [`start`](runtime.md#start) operation is called but [before the user-specified program command is executed](runtime.md#lifecycle)." + PrestartTiming + // PoststartTiming represents "The post-start hooks MUST be called [after the user-specified process is executed](runtime.md#lifecycle) but before the [`start`](runtime.md#start) operation returns." + PoststartTiming + // PoststopTiming represents "The post-stop hooks MUST be called [after the container is deleted](runtime.md#lifecycle) but before the [`delete`](runtime.md#delete) operation returns." + PoststopTiming + // AnnotationsKeyValueMap represents "Annotations MUST be a key-value map." + AnnotationsKeyValueMap + // AnnotationsKeyString represents "Keys MUST be strings." + AnnotationsKeyString + // AnnotationsKeyRequired represents "Keys MUST NOT be an empty string." + AnnotationsKeyRequired + // AnnotationsKeyReversedDomain represents "Keys SHOULD be named using a reverse domain notation - e.g. `com.example.myKey`." + AnnotationsKeyReversedDomain + // AnnotationsKeyReservedNS represents "Keys using the `org.opencontainers` namespace are reserved and MUST NOT be used by subsequent specifications." + AnnotationsKeyReservedNS + // AnnotationsKeyIgnoreUnknown represents "Implementations that are reading/processing this configuration file MUST NOT generate an error if they encounter an unknown annotation key." + AnnotationsKeyIgnoreUnknown + // AnnotationsValueString represents "Values MUST be strings." + AnnotationsValueString + // ExtensibilityIgnoreUnknownProp represents "Runtimes that are reading or processing this configuration file MUST NOT generate an error if they encounter an unknown property." + ExtensibilityIgnoreUnknownProp + // ValidValuesError represents "Runtimes that are reading or processing this configuration file MUST generate an error when invalid or unsupported values are encountered." + ValidValuesError // DefaultFilesystems represents the error code of default filesystems test DefaultFilesystems @@ -55,56 +122,140 @@ const ( 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) { + + specificationVersionRef = 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 } + mountsRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#mounts"), nil + } + processRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#process"), nil + } + posixProcRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#posix-process"), nil + } + linuxProcRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#linux-process"), nil + } + platformSpecificConfigurationRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#platform-specific-configuration"), nil + } + posixPlatformHooksRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#posix-platform-hooks"), nil + } + prestartRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#prestart"), nil + } + poststartRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#poststart"), nil + } + poststopRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#poststop"), nil + } + annotationsRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#annotations"), nil + } + extensibilityRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#extensibility"), nil + } + validValuesRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#valid-values"), 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 } ) +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 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}, + // config.md + // Specification version + SpecVersionInSemVer: {Level: rfc2119.Must, Reference: specificationVersionRef}, // 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}, + RootOnWindowsRequired: {Level: rfc2119.Required, Reference: rootRef}, + RootOnHyperVNotSet: {Level: rfc2119.Must, Reference: rootRef}, + RootOnNonHyperVRequired: {Level: rfc2119.Required, Reference: rootRef}, + RootPathOnWindowsGUID: {Level: rfc2119.Must, Reference: rootRef}, + RootPathOnPosixConvention: {Level: rfc2119.Should, Reference: rootRef}, + RootPathExist: {Level: rfc2119.Must, Reference: rootRef}, + RootReadonlyImplement: {Level: rfc2119.Must, Reference: rootRef}, + RootReadonlyOnWindowsFalse: {Level: rfc2119.Must, Reference: rootRef}, + // Mounts + MountsInOrder: {Level: rfc2119.Must, Reference: mountsRef}, + MountsDestAbs: {Level: rfc2119.Must, Reference: mountsRef}, + MountsDestOnWindowsNotNested: {Level: rfc2119.Must, Reference: mountsRef}, + MountsOptionsOnWindowsROSupport: {Level: rfc2119.Must, Reference: mountsRef}, + // Proc + ProcRequiredAtStart: {Level: rfc2119.Required, Reference: processRef}, + ProcConsoleSizeIgnore: {Level: rfc2119.Must, Reference: processRef}, + ProcCwdAbs: {Level: rfc2119.Must, Reference: processRef}, + ProcArgsOneEntryRequired: {Level: rfc2119.Required, Reference: processRef}, + // POSIX process + PosixProcRlimitsTypeError: {Level: rfc2119.Must, Reference: posixProcRef}, + PosixProcRlimitsTypeGet: {Level: rfc2119.Must, Reference: posixProcRef}, + PosixProcRlimitsSoftMatchCur: {Level: rfc2119.Must, Reference: posixProcRef}, + PosixProcRlimitsHardMatchMax: {Level: rfc2119.Must, Reference: posixProcRef}, + PosixProcRlimitsErrorOnDup: {Level: rfc2119.Must, Reference: posixProcRef}, + // Linux Proc + LinuxProcCapError: {Level: rfc2119.Must, Reference: linuxProcRef}, + LinuxProcOomScoreAdjSet: {Level: rfc2119.Must, Reference: linuxProcRef}, + LinuxProcOomScoreAdjNotSet: {Level: rfc2119.Must, Reference: linuxProcRef}, + // Platform-specific configuration + PlatformSpecConfOnWindowsSet: {Level: rfc2119.Must, Reference: platformSpecificConfigurationRef}, + // POSIX-platform Hooks + PosixHooksPathAbs: {Level: rfc2119.Must, Reference: posixPlatformHooksRef}, + PosixHooksTimeoutPositive: {Level: rfc2119.Must, Reference: posixPlatformHooksRef}, + PosixHooksCalledInOrder: {Level: rfc2119.Must, Reference: posixPlatformHooksRef}, + PosixHooksStateToStdin: {Level: rfc2119.Must, Reference: posixPlatformHooksRef}, + // Prestart + PrestartTiming: {Level: rfc2119.Must, Reference: prestartRef}, + // Poststart + PoststartTiming: {Level: rfc2119.Must, Reference: poststartRef}, + // Poststop + PoststopTiming: {Level: rfc2119.Must, Reference: poststopRef}, + // Annotations + AnnotationsKeyValueMap: {Level: rfc2119.Must, Reference: annotationsRef}, + AnnotationsKeyString: {Level: rfc2119.Must, Reference: annotationsRef}, + AnnotationsKeyRequired: {Level: rfc2119.Must, Reference: annotationsRef}, + AnnotationsKeyReversedDomain: {Level: rfc2119.Should, Reference: annotationsRef}, + AnnotationsKeyReservedNS: {Level: rfc2119.Must, Reference: annotationsRef}, + AnnotationsKeyIgnoreUnknown: {Level: rfc2119.Must, Reference: annotationsRef}, + AnnotationsValueString: {Level: rfc2119.Must, Reference: annotationsRef}, + // Extensibility + ExtensibilityIgnoreUnknownProp: {Level: rfc2119.Must, Reference: extensibilityRef}, + // Valid values + ValidValuesError: {Level: rfc2119.Must, Reference: validValuesRef}, // Config-Linux.md // Default Filesystems diff --git a/validate/validate.go b/validate/validate.go index 0d62a53ef..1bbec849f 100644 --- a/validate/validate.go +++ b/validate/validate.go @@ -120,13 +120,13 @@ func (v *Validator) CheckRoot() (errs error) { if v.platform == "windows" && v.spec.Windows != nil && v.spec.Windows.HyperV != nil { if v.spec.Root != nil { errs = multierror.Append(errs, - specerror.NewError(specerror.RootOnHyperV, fmt.Errorf("for Hyper-V containers, Root must not be set"), rspec.Version)) + specerror.NewError(specerror.RootOnHyperVNotSet, fmt.Errorf("for Hyper-V containers, Root must not be set"), rspec.Version)) return } return } else if v.spec.Root == nil { errs = multierror.Append(errs, - specerror.NewError(specerror.RootOnNonHyperV, fmt.Errorf("for non-Hyper-V containers, Root must be set"), rspec.Version)) + specerror.NewError(specerror.RootOnNonHyperVRequired, fmt.Errorf("for non-Hyper-V containers, Root must be set"), rspec.Version)) return } @@ -138,7 +138,7 @@ func (v *Validator) CheckRoot() (errs error) { if filepath.Base(v.spec.Root.Path) != "rootfs" { errs = multierror.Append(errs, - specerror.NewError(specerror.PathName, fmt.Errorf("path name should be the conventional 'rootfs'"), rspec.Version)) + specerror.NewError(specerror.RootPathOnPosixConvention, fmt.Errorf("path name should be the conventional 'rootfs'"), rspec.Version)) } var rootfsPath string @@ -158,10 +158,10 @@ func (v *Validator) CheckRoot() (errs error) { if fi, err := os.Stat(rootfsPath); err != nil { errs = multierror.Append(errs, - specerror.NewError(specerror.PathExistence, fmt.Errorf("cannot find the root path %q", rootfsPath), rspec.Version)) + specerror.NewError(specerror.RootPathExist, fmt.Errorf("cannot find the root path %q", rootfsPath), rspec.Version)) } else if !fi.IsDir() { errs = multierror.Append(errs, - specerror.NewError(specerror.PathExistence, fmt.Errorf("root.path %q is not a directory", rootfsPath), rspec.Version)) + specerror.NewError(specerror.RootPathExist, fmt.Errorf("root.path %q is not a directory", rootfsPath), rspec.Version)) } rootParent := filepath.Dir(absRootPath) @@ -173,7 +173,7 @@ func (v *Validator) CheckRoot() (errs error) { if v.platform == "windows" { if v.spec.Root.Readonly { errs = multierror.Append(errs, - specerror.NewError(specerror.ReadonlyOnWindows, fmt.Errorf("root.readonly field MUST be omitted or false when target platform is windows"), rspec.Version)) + specerror.NewError(specerror.RootReadonlyOnWindowsFalse, fmt.Errorf("root.readonly field MUST be omitted or false when target platform is windows"), rspec.Version)) } } @@ -188,7 +188,7 @@ func (v *Validator) CheckSemVer() (errs error) { _, err := semver.Parse(version) if err != nil { errs = multierror.Append(errs, - specerror.NewError(specerror.SpecVersion, fmt.Errorf("%q is not valid SemVer: %s", version, err.Error()), rspec.Version)) + specerror.NewError(specerror.SpecVersionInSemVer, fmt.Errorf("%q is not valid SemVer: %s", version, err.Error()), rspec.Version)) } if version != rspec.Version { errs = multierror.Append(errs, fmt.Errorf("validate currently only handles version %s, but the supplied configuration targets %s", rspec.Version, version)) diff --git a/validate/validate_test.go b/validate/validate_test.go index 45e351eb7..6fa62525b 100644 --- a/validate/validate_test.go +++ b/validate/validate_test.go @@ -55,16 +55,16 @@ func TestCheckRoot(t *testing.T) { platform string expected specerror.Code }{ - {rspec.Spec{Windows: &rspec.Windows{HyperV: &rspec.WindowsHyperV{}}, Root: &rspec.Root{}}, "windows", specerror.RootOnHyperV}, + {rspec.Spec{Windows: &rspec.Windows{HyperV: &rspec.WindowsHyperV{}}, Root: &rspec.Root{}}, "windows", specerror.RootOnHyperVNotSet}, {rspec.Spec{Windows: &rspec.Windows{HyperV: &rspec.WindowsHyperV{}}, Root: nil}, "windows", specerror.NonError}, - {rspec.Spec{Root: nil}, "linux", specerror.RootOnNonHyperV}, - {rspec.Spec{Root: &rspec.Root{Path: "maverick-rootfs"}}, "linux", specerror.PathName}, + {rspec.Spec{Root: nil}, "linux", specerror.RootOnNonHyperVRequired}, + {rspec.Spec{Root: &rspec.Root{Path: "maverick-rootfs"}}, "linux", specerror.RootPathOnPosixConvention}, {rspec.Spec{Root: &rspec.Root{Path: "rootfs"}}, "linux", specerror.NonError}, - {rspec.Spec{Root: &rspec.Root{Path: filepath.Join(tmpBundle, rootfsNonExists)}}, "linux", specerror.PathExistence}, - {rspec.Spec{Root: &rspec.Root{Path: filepath.Join(tmpBundle, rootfsNonDir)}}, "linux", specerror.PathExistence}, + {rspec.Spec{Root: &rspec.Root{Path: filepath.Join(tmpBundle, rootfsNonExists)}}, "linux", specerror.RootPathExist}, + {rspec.Spec{Root: &rspec.Root{Path: filepath.Join(tmpBundle, rootfsNonDir)}}, "linux", specerror.RootPathExist}, {rspec.Spec{Root: &rspec.Root{Path: filepath.Join(tmpBundle, "rootfs")}}, "linux", specerror.NonError}, {rspec.Spec{Root: &rspec.Root{Path: "rootfs/rootfs"}}, "linux", specerror.ArtifactsInSingleDir}, - {rspec.Spec{Root: &rspec.Root{Readonly: true}}, "windows", specerror.ReadonlyOnWindows}, + {rspec.Spec{Root: &rspec.Root{Readonly: true}}, "windows", specerror.RootReadonlyOnWindowsFalse}, } for _, c := range cases { v := NewValidator(&c.val, tmpBundle, false, c.platform) @@ -81,7 +81,7 @@ func TestCheckSemVer(t *testing.T) { {rspec.Version, specerror.NonError}, //FIXME: validate currently only handles rpsec.Version {"0.0.1", specerror.NonRFCError}, - {"invalid", specerror.SpecVersion}, + {"invalid", specerror.SpecVersionInSemVer}, } for _, c := range cases {