container_create: handle cap add/drop ALL
Kubelet can send cap add/drop ALL. Handle that in CRI-O as well. Also, this PR is re-vendoring runtime-tools to fix capabilities add to add caps to _all_ caps set **and** fix a shared memory issue (caps set were initialized with the same slice, if one modifies one slice, it's reflected on the other slices, the vendoring fixes this as well) Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
parent
7f4f630b98
commit
af0a494251
25 changed files with 2057 additions and 283 deletions
92
vendor/github.com/opencontainers/runtime-tools/error/error.go
generated
vendored
Normal file
92
vendor/github.com/opencontainers/runtime-tools/error/error.go
generated
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
// 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 RFC 2119 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 specification reference.
|
||||
type Error struct {
|
||||
// Level represents the RFC 2119 compliance level.
|
||||
Level Level
|
||||
|
||||
// Reference is a URL for the violated specification requirement.
|
||||
Reference string
|
||||
|
||||
// Err holds additional details about the violation.
|
||||
Err error
|
||||
}
|
||||
|
||||
// ParseLevel takes a string level and returns the RFC 2119 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 specification reference.
|
||||
func (err *Error) Error() string {
|
||||
return fmt.Sprintf("%s\nRefer to: %s", err.Err.Error(), err.Reference)
|
||||
}
|
163
vendor/github.com/opencontainers/runtime-tools/generate/generate.go
generated
vendored
163
vendor/github.com/opencontainers/runtime-tools/generate/generate.go
generated
vendored
|
@ -744,6 +744,38 @@ func (g *Generator) ClearPreStartHooks() {
|
|||
func (g *Generator) AddPreStartHook(path string, args []string) {
|
||||
g.initSpecHooks()
|
||||
hook := rspec.Hook{Path: path, Args: args}
|
||||
for i, hook := range g.spec.Hooks.Prestart {
|
||||
if hook.Path == path {
|
||||
g.spec.Hooks.Prestart[i] = hook
|
||||
return
|
||||
}
|
||||
}
|
||||
g.spec.Hooks.Prestart = append(g.spec.Hooks.Prestart, hook)
|
||||
}
|
||||
|
||||
// AddPreStartHookEnv adds envs of a prestart hook into g.spec.Hooks.Prestart.
|
||||
func (g *Generator) AddPreStartHookEnv(path string, envs []string) {
|
||||
g.initSpecHooks()
|
||||
for i, hook := range g.spec.Hooks.Prestart {
|
||||
if hook.Path == path {
|
||||
g.spec.Hooks.Prestart[i].Env = envs
|
||||
return
|
||||
}
|
||||
}
|
||||
hook := rspec.Hook{Path: path, Env: envs}
|
||||
g.spec.Hooks.Prestart = append(g.spec.Hooks.Prestart, hook)
|
||||
}
|
||||
|
||||
// AddPreStartHookTimeout adds timeout of a prestart hook into g.spec.Hooks.Prestart.
|
||||
func (g *Generator) AddPreStartHookTimeout(path string, timeout int) {
|
||||
g.initSpecHooks()
|
||||
for i, hook := range g.spec.Hooks.Prestart {
|
||||
if hook.Path == path {
|
||||
g.spec.Hooks.Prestart[i].Timeout = &timeout
|
||||
return
|
||||
}
|
||||
}
|
||||
hook := rspec.Hook{Path: path, Timeout: &timeout}
|
||||
g.spec.Hooks.Prestart = append(g.spec.Hooks.Prestart, hook)
|
||||
}
|
||||
|
||||
|
@ -762,6 +794,38 @@ func (g *Generator) ClearPostStopHooks() {
|
|||
func (g *Generator) AddPostStopHook(path string, args []string) {
|
||||
g.initSpecHooks()
|
||||
hook := rspec.Hook{Path: path, Args: args}
|
||||
for i, hook := range g.spec.Hooks.Poststop {
|
||||
if hook.Path == path {
|
||||
g.spec.Hooks.Poststop[i] = hook
|
||||
return
|
||||
}
|
||||
}
|
||||
g.spec.Hooks.Poststop = append(g.spec.Hooks.Poststop, hook)
|
||||
}
|
||||
|
||||
// AddPostStopHookEnv adds envs of a poststop hook into g.spec.Hooks.Poststop.
|
||||
func (g *Generator) AddPostStopHookEnv(path string, envs []string) {
|
||||
g.initSpecHooks()
|
||||
for i, hook := range g.spec.Hooks.Poststop {
|
||||
if hook.Path == path {
|
||||
g.spec.Hooks.Poststop[i].Env = envs
|
||||
return
|
||||
}
|
||||
}
|
||||
hook := rspec.Hook{Path: path, Env: envs}
|
||||
g.spec.Hooks.Poststop = append(g.spec.Hooks.Poststop, hook)
|
||||
}
|
||||
|
||||
// AddPostStopHookTimeout adds timeout of a poststop hook into g.spec.Hooks.Poststop.
|
||||
func (g *Generator) AddPostStopHookTimeout(path string, timeout int) {
|
||||
g.initSpecHooks()
|
||||
for i, hook := range g.spec.Hooks.Poststop {
|
||||
if hook.Path == path {
|
||||
g.spec.Hooks.Poststop[i].Timeout = &timeout
|
||||
return
|
||||
}
|
||||
}
|
||||
hook := rspec.Hook{Path: path, Timeout: &timeout}
|
||||
g.spec.Hooks.Poststop = append(g.spec.Hooks.Poststop, hook)
|
||||
}
|
||||
|
||||
|
@ -780,6 +844,38 @@ func (g *Generator) ClearPostStartHooks() {
|
|||
func (g *Generator) AddPostStartHook(path string, args []string) {
|
||||
g.initSpecHooks()
|
||||
hook := rspec.Hook{Path: path, Args: args}
|
||||
for i, hook := range g.spec.Hooks.Poststart {
|
||||
if hook.Path == path {
|
||||
g.spec.Hooks.Poststart[i] = hook
|
||||
return
|
||||
}
|
||||
}
|
||||
g.spec.Hooks.Poststart = append(g.spec.Hooks.Poststart, hook)
|
||||
}
|
||||
|
||||
// AddPostStartHookEnv adds envs of a poststart hook into g.spec.Hooks.Poststart.
|
||||
func (g *Generator) AddPostStartHookEnv(path string, envs []string) {
|
||||
g.initSpecHooks()
|
||||
for i, hook := range g.spec.Hooks.Poststart {
|
||||
if hook.Path == path {
|
||||
g.spec.Hooks.Poststart[i].Env = envs
|
||||
return
|
||||
}
|
||||
}
|
||||
hook := rspec.Hook{Path: path, Env: envs}
|
||||
g.spec.Hooks.Poststart = append(g.spec.Hooks.Poststart, hook)
|
||||
}
|
||||
|
||||
// AddPostStartHookTimeout adds timeout of a poststart hook into g.spec.Hooks.Poststart.
|
||||
func (g *Generator) AddPostStartHookTimeout(path string, timeout int) {
|
||||
g.initSpecHooks()
|
||||
for i, hook := range g.spec.Hooks.Poststart {
|
||||
if hook.Path == path {
|
||||
g.spec.Hooks.Poststart[i].Timeout = &timeout
|
||||
return
|
||||
}
|
||||
}
|
||||
hook := rspec.Hook{Path: path, Timeout: &timeout}
|
||||
g.spec.Hooks.Poststart = append(g.spec.Hooks.Poststart, hook)
|
||||
}
|
||||
|
||||
|
@ -860,11 +956,12 @@ func (g *Generator) SetupPrivileged(privileged bool) {
|
|||
}
|
||||
g.initSpecLinux()
|
||||
g.initSpecProcessCapabilities()
|
||||
g.spec.Process.Capabilities.Bounding = finalCapList
|
||||
g.spec.Process.Capabilities.Effective = finalCapList
|
||||
g.spec.Process.Capabilities.Inheritable = finalCapList
|
||||
g.spec.Process.Capabilities.Permitted = finalCapList
|
||||
g.spec.Process.Capabilities.Ambient = finalCapList
|
||||
g.ClearProcessCapabilities()
|
||||
g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding, finalCapList...)
|
||||
g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective, finalCapList...)
|
||||
g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable, finalCapList...)
|
||||
g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted, finalCapList...)
|
||||
g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient, finalCapList...)
|
||||
g.spec.Process.SelinuxLabel = ""
|
||||
g.spec.Process.ApparmorProfile = ""
|
||||
g.spec.Linux.Seccomp = nil
|
||||
|
@ -892,40 +989,60 @@ func (g *Generator) AddProcessCapability(c string) error {
|
|||
|
||||
g.initSpecProcessCapabilities()
|
||||
|
||||
var foundBounding bool
|
||||
for _, cap := range g.spec.Process.Capabilities.Bounding {
|
||||
if strings.ToUpper(cap) == cp {
|
||||
return nil
|
||||
foundBounding = true
|
||||
break
|
||||
}
|
||||
}
|
||||
g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding, cp)
|
||||
if !foundBounding {
|
||||
g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding, cp)
|
||||
}
|
||||
|
||||
var foundEffective bool
|
||||
for _, cap := range g.spec.Process.Capabilities.Effective {
|
||||
if strings.ToUpper(cap) == cp {
|
||||
return nil
|
||||
foundEffective = true
|
||||
break
|
||||
}
|
||||
}
|
||||
g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective, cp)
|
||||
if !foundEffective {
|
||||
g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective, cp)
|
||||
}
|
||||
|
||||
var foundInheritable bool
|
||||
for _, cap := range g.spec.Process.Capabilities.Inheritable {
|
||||
if strings.ToUpper(cap) == cp {
|
||||
return nil
|
||||
foundInheritable = true
|
||||
break
|
||||
}
|
||||
}
|
||||
g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable, cp)
|
||||
if !foundInheritable {
|
||||
g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable, cp)
|
||||
}
|
||||
|
||||
var foundPermitted bool
|
||||
for _, cap := range g.spec.Process.Capabilities.Permitted {
|
||||
if strings.ToUpper(cap) == cp {
|
||||
return nil
|
||||
foundPermitted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted, cp)
|
||||
if !foundPermitted {
|
||||
g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted, cp)
|
||||
}
|
||||
|
||||
var foundAmbient bool
|
||||
for _, cap := range g.spec.Process.Capabilities.Ambient {
|
||||
if strings.ToUpper(cap) == cp {
|
||||
return nil
|
||||
foundAmbient = true
|
||||
break
|
||||
}
|
||||
}
|
||||
g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient, cp)
|
||||
if !foundAmbient {
|
||||
g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient, cp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -939,33 +1056,39 @@ func (g *Generator) DropProcessCapability(c string) error {
|
|||
|
||||
g.initSpecProcessCapabilities()
|
||||
|
||||
// we don't care about order...and this is way faster...
|
||||
removeFunc := func(s []string, i int) []string {
|
||||
s[i] = s[len(s)-1]
|
||||
return s[:len(s)-1]
|
||||
}
|
||||
|
||||
for i, cap := range g.spec.Process.Capabilities.Bounding {
|
||||
if strings.ToUpper(cap) == cp {
|
||||
g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding[:i], g.spec.Process.Capabilities.Bounding[i+1:]...)
|
||||
g.spec.Process.Capabilities.Bounding = removeFunc(g.spec.Process.Capabilities.Bounding, i)
|
||||
}
|
||||
}
|
||||
|
||||
for i, cap := range g.spec.Process.Capabilities.Effective {
|
||||
if strings.ToUpper(cap) == cp {
|
||||
g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective[:i], g.spec.Process.Capabilities.Effective[i+1:]...)
|
||||
g.spec.Process.Capabilities.Effective = removeFunc(g.spec.Process.Capabilities.Effective, i)
|
||||
}
|
||||
}
|
||||
|
||||
for i, cap := range g.spec.Process.Capabilities.Inheritable {
|
||||
if strings.ToUpper(cap) == cp {
|
||||
g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable[:i], g.spec.Process.Capabilities.Inheritable[i+1:]...)
|
||||
g.spec.Process.Capabilities.Inheritable = removeFunc(g.spec.Process.Capabilities.Inheritable, i)
|
||||
}
|
||||
}
|
||||
|
||||
for i, cap := range g.spec.Process.Capabilities.Permitted {
|
||||
if strings.ToUpper(cap) == cp {
|
||||
g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted[:i], g.spec.Process.Capabilities.Permitted[i+1:]...)
|
||||
g.spec.Process.Capabilities.Permitted = removeFunc(g.spec.Process.Capabilities.Permitted, i)
|
||||
}
|
||||
}
|
||||
|
||||
for i, cap := range g.spec.Process.Capabilities.Ambient {
|
||||
if strings.ToUpper(cap) == cp {
|
||||
g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient[:i], g.spec.Process.Capabilities.Ambient[i+1:]...)
|
||||
g.spec.Process.Capabilities.Ambient = removeFunc(g.spec.Process.Capabilities.Ambient, i)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
170
vendor/github.com/opencontainers/runtime-tools/specerror/error.go
generated
vendored
Normal file
170
vendor/github.com/opencontainers/runtime-tools/specerror/error.go
generated
vendored
Normal file
|
@ -0,0 +1,170 @@
|
|||
// 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
|
||||
}
|
110
vendor/github.com/opencontainers/runtime-tools/validate/error.go
generated
vendored
110
vendor/github.com/opencontainers/runtime-tools/validate/error.go
generated
vendored
|
@ -1,110 +0,0 @@
|
|||
package validate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
rspec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
// ComplianceLevel represents the OCI compliance levels
|
||||
type ComplianceLevel int
|
||||
|
||||
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
|
||||
type ErrorCode int
|
||||
|
||||
const (
|
||||
// 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
|
||||
}
|
||||
|
||||
const referencePrefix = "https://github.com/opencontainers/runtime-spec/blob"
|
||||
|
||||
var ociErrors = map[ErrorCode]Error{
|
||||
DefaultFilesystems: Error{Level: ComplianceShould, Reference: "config-linux.md#default-filesystems"},
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
435
vendor/github.com/opencontainers/runtime-tools/validate/validate.go
generated
vendored
435
vendor/github.com/opencontainers/runtime-tools/validate/validate.go
generated
vendored
|
@ -3,21 +3,27 @@ package validate
|
|||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
rspec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
|
||||
"github.com/opencontainers/runtime-tools/specerror"
|
||||
)
|
||||
|
||||
const specConfig = "config.json"
|
||||
|
@ -70,7 +76,7 @@ func NewValidatorFromPath(bundlePath string, hostSpecific bool, platform string)
|
|||
platform = runtime.GOOS
|
||||
}
|
||||
if bundlePath == "" {
|
||||
return Validator{}, fmt.Errorf("Bundle path shouldn't be empty")
|
||||
return Validator{}, fmt.Errorf("bundle path shouldn't be empty")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(bundlePath); err != nil {
|
||||
|
@ -80,7 +86,7 @@ func NewValidatorFromPath(bundlePath string, hostSpecific bool, platform string)
|
|||
configPath := filepath.Join(bundlePath, specConfig)
|
||||
content, err := ioutil.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return Validator{}, err
|
||||
return Validator{}, specerror.NewError(specerror.ConfigFileExistence, err, rspec.Version)
|
||||
}
|
||||
if !utf8.Valid(content) {
|
||||
return Validator{}, fmt.Errorf("%q is not encoded in UTF-8", configPath)
|
||||
|
@ -94,37 +100,45 @@ func NewValidatorFromPath(bundlePath string, hostSpecific bool, platform string)
|
|||
}
|
||||
|
||||
// CheckAll checks all parts of runtime bundle
|
||||
func (v *Validator) CheckAll() (msgs []string) {
|
||||
msgs = append(msgs, v.CheckPlatform()...)
|
||||
msgs = append(msgs, v.CheckRoot()...)
|
||||
msgs = append(msgs, v.CheckMandatoryFields()...)
|
||||
msgs = append(msgs, v.CheckSemVer()...)
|
||||
msgs = append(msgs, v.CheckMounts()...)
|
||||
msgs = append(msgs, v.CheckProcess()...)
|
||||
msgs = append(msgs, v.CheckHooks()...)
|
||||
msgs = append(msgs, v.CheckLinux()...)
|
||||
func (v *Validator) CheckAll() (errs error) {
|
||||
errs = multierror.Append(errs, v.CheckPlatform())
|
||||
errs = multierror.Append(errs, v.CheckRoot())
|
||||
errs = multierror.Append(errs, v.CheckMandatoryFields())
|
||||
errs = multierror.Append(errs, v.CheckSemVer())
|
||||
errs = multierror.Append(errs, v.CheckMounts())
|
||||
errs = multierror.Append(errs, v.CheckProcess())
|
||||
errs = multierror.Append(errs, v.CheckHooks())
|
||||
errs = multierror.Append(errs, v.CheckLinux())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CheckRoot checks status of v.spec.Root
|
||||
func (v *Validator) CheckRoot() (msgs []string) {
|
||||
func (v *Validator) CheckRoot() (errs error) {
|
||||
logrus.Debugf("check root")
|
||||
|
||||
if v.platform == "windows" && v.spec.Windows.HyperV != nil {
|
||||
if v.platform == "windows" && v.spec.Windows != nil && v.spec.Windows.HyperV != nil {
|
||||
if v.spec.Root != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("for Hyper-V containers, Root must not be set"))
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(specerror.RootOnHyperV, fmt.Errorf("for Hyper-V containers, Root must not be set"), rspec.Version))
|
||||
return
|
||||
}
|
||||
return
|
||||
} else if v.spec.Root == nil {
|
||||
msgs = append(msgs, fmt.Sprintf("for non-Hyper-V containers, Root must be set"))
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(specerror.RootOnNonHyperV, fmt.Errorf("for non-Hyper-V containers, Root must be set"), rspec.Version))
|
||||
return
|
||||
}
|
||||
|
||||
absBundlePath, err := filepath.Abs(v.bundlePath)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("unable to convert %q to an absolute path", v.bundlePath))
|
||||
errs = multierror.Append(errs, fmt.Errorf("unable to convert %q to an absolute path", v.bundlePath))
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
var rootfsPath string
|
||||
|
@ -137,24 +151,29 @@ func (v *Validator) CheckRoot() (msgs []string) {
|
|||
rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path)
|
||||
absRootPath, err = filepath.Abs(rootfsPath)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("unable to convert %q to an absolute path", rootfsPath))
|
||||
errs = multierror.Append(errs, fmt.Errorf("unable to convert %q to an absolute path", rootfsPath))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if fi, err := os.Stat(rootfsPath); err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("Cannot find the root path %q", rootfsPath))
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(specerror.PathExistence, fmt.Errorf("cannot find the root path %q", rootfsPath), rspec.Version))
|
||||
} else if !fi.IsDir() {
|
||||
msgs = append(msgs, fmt.Sprintf("The root path %q is not a directory.", rootfsPath))
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(specerror.PathExistence, fmt.Errorf("root.path %q is not a directory", rootfsPath), rspec.Version))
|
||||
}
|
||||
|
||||
rootParent := filepath.Dir(absRootPath)
|
||||
if absRootPath == string(filepath.Separator) || rootParent != absBundlePath {
|
||||
msgs = append(msgs, fmt.Sprintf("root.path is %q, but it MUST be a child of %q", v.spec.Root.Path, absBundlePath))
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(specerror.ArtifactsInSingleDir, fmt.Errorf("root.path is %q, but it MUST be a child of %q", v.spec.Root.Path, absBundlePath), rspec.Version))
|
||||
}
|
||||
|
||||
if v.platform == "windows" {
|
||||
if v.spec.Root.Readonly {
|
||||
msgs = append(msgs, "root.readonly field MUST be omitted or false when target platform is windows")
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,53 +181,54 @@ func (v *Validator) CheckRoot() (msgs []string) {
|
|||
}
|
||||
|
||||
// CheckSemVer checks v.spec.Version
|
||||
func (v *Validator) CheckSemVer() (msgs []string) {
|
||||
func (v *Validator) CheckSemVer() (errs error) {
|
||||
logrus.Debugf("check semver")
|
||||
|
||||
version := v.spec.Version
|
||||
_, err := semver.Parse(version)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("%q is not valid SemVer: %s", version, err.Error()))
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(specerror.SpecVersion, fmt.Errorf("%q is not valid SemVer: %s", version, err.Error()), rspec.Version))
|
||||
}
|
||||
if version != rspec.Version {
|
||||
msgs = append(msgs, fmt.Sprintf("internal error: validate currently only handles version %s, but the supplied configuration targets %s", rspec.Version, version))
|
||||
errs = multierror.Append(errs, fmt.Errorf("validate currently only handles version %s, but the supplied configuration targets %s", rspec.Version, version))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CheckHooks check v.spec.Hooks
|
||||
func (v *Validator) CheckHooks() (msgs []string) {
|
||||
func (v *Validator) CheckHooks() (errs error) {
|
||||
logrus.Debugf("check hooks")
|
||||
|
||||
if v.spec.Hooks != nil {
|
||||
msgs = append(msgs, checkEventHooks("pre-start", v.spec.Hooks.Prestart, v.HostSpecific)...)
|
||||
msgs = append(msgs, checkEventHooks("post-start", v.spec.Hooks.Poststart, v.HostSpecific)...)
|
||||
msgs = append(msgs, checkEventHooks("post-stop", v.spec.Hooks.Poststop, v.HostSpecific)...)
|
||||
errs = multierror.Append(errs, checkEventHooks("pre-start", v.spec.Hooks.Prestart, v.HostSpecific))
|
||||
errs = multierror.Append(errs, checkEventHooks("post-start", v.spec.Hooks.Poststart, v.HostSpecific))
|
||||
errs = multierror.Append(errs, checkEventHooks("post-stop", v.spec.Hooks.Poststop, v.HostSpecific))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (msgs []string) {
|
||||
func checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (errs error) {
|
||||
for _, hook := range hooks {
|
||||
if !filepath.IsAbs(hook.Path) {
|
||||
msgs = append(msgs, fmt.Sprintf("The %s hook %v: is not absolute path", hookType, hook.Path))
|
||||
errs = multierror.Append(errs, fmt.Errorf("the %s hook %v: is not absolute path", hookType, hook.Path))
|
||||
}
|
||||
|
||||
if hostSpecific {
|
||||
fi, err := os.Stat(hook.Path)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("Cannot find %s hook: %v", hookType, hook.Path))
|
||||
errs = multierror.Append(errs, fmt.Errorf("cannot find %s hook: %v", hookType, hook.Path))
|
||||
}
|
||||
if fi.Mode()&0111 == 0 {
|
||||
msgs = append(msgs, fmt.Sprintf("The %s hook %v: is not executable", hookType, hook.Path))
|
||||
errs = multierror.Append(errs, fmt.Errorf("the %s hook %v: is not executable", hookType, hook.Path))
|
||||
}
|
||||
}
|
||||
|
||||
for _, env := range hook.Env {
|
||||
if !envValid(env) {
|
||||
msgs = append(msgs, fmt.Sprintf("Env %q for hook %v is in the invalid form.", env, hook.Path))
|
||||
errs = multierror.Append(errs, fmt.Errorf("env %q for hook %v is in the invalid form", env, hook.Path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +237,7 @@ func checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (ms
|
|||
}
|
||||
|
||||
// CheckProcess checks v.spec.Process
|
||||
func (v *Validator) CheckProcess() (msgs []string) {
|
||||
func (v *Validator) CheckProcess() (errs error) {
|
||||
logrus.Debugf("check process")
|
||||
|
||||
if v.spec.Process == nil {
|
||||
|
@ -226,17 +246,17 @@ func (v *Validator) CheckProcess() (msgs []string) {
|
|||
|
||||
process := v.spec.Process
|
||||
if !filepath.IsAbs(process.Cwd) {
|
||||
msgs = append(msgs, fmt.Sprintf("cwd %q is not an absolute path", process.Cwd))
|
||||
errs = multierror.Append(errs, fmt.Errorf("cwd %q is not an absolute path", process.Cwd))
|
||||
}
|
||||
|
||||
for _, env := range process.Env {
|
||||
if !envValid(env) {
|
||||
msgs = append(msgs, fmt.Sprintf("env %q should be in the form of 'key=value'. The left hand side must consist solely of letters, digits, and underscores '_'.", env))
|
||||
errs = multierror.Append(errs, fmt.Errorf("env %q should be in the form of 'key=value'. The left hand side must consist solely of letters, digits, and underscores '_'", env))
|
||||
}
|
||||
}
|
||||
|
||||
if len(process.Args) == 0 {
|
||||
msgs = append(msgs, fmt.Sprintf("args must not be empty"))
|
||||
errs = multierror.Append(errs, fmt.Errorf("args must not be empty"))
|
||||
} else {
|
||||
if filepath.IsAbs(process.Args[0]) {
|
||||
var rootfsPath string
|
||||
|
@ -250,27 +270,27 @@ func (v *Validator) CheckProcess() (msgs []string) {
|
|||
if os.IsNotExist(err) {
|
||||
logrus.Warnf("executable %q is not available in rootfs currently", process.Args[0])
|
||||
} else if err != nil {
|
||||
msgs = append(msgs, err.Error())
|
||||
errs = multierror.Append(errs, err)
|
||||
} else {
|
||||
m := fileinfo.Mode()
|
||||
if m.IsDir() || m&0111 == 0 {
|
||||
msgs = append(msgs, fmt.Sprintf("arg %q is not executable", process.Args[0]))
|
||||
errs = multierror.Append(errs, fmt.Errorf("arg %q is not executable", process.Args[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v.spec.Process.Capabilities != nil {
|
||||
msgs = append(msgs, v.CheckCapabilities()...)
|
||||
errs = multierror.Append(errs, v.CheckCapabilities())
|
||||
}
|
||||
msgs = append(msgs, v.CheckRlimits()...)
|
||||
errs = multierror.Append(errs, v.CheckRlimits())
|
||||
|
||||
if v.platform == "linux" {
|
||||
if len(process.ApparmorProfile) > 0 {
|
||||
profilePath := filepath.Join(v.bundlePath, v.spec.Root.Path, "/etc/apparmor.d", process.ApparmorProfile)
|
||||
_, err := os.Stat(profilePath)
|
||||
if err != nil {
|
||||
msgs = append(msgs, err.Error())
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -279,7 +299,7 @@ func (v *Validator) CheckProcess() (msgs []string) {
|
|||
}
|
||||
|
||||
// CheckCapabilities checks v.spec.Process.Capabilities
|
||||
func (v *Validator) CheckCapabilities() (msgs []string) {
|
||||
func (v *Validator) CheckCapabilities() (errs error) {
|
||||
process := v.spec.Process
|
||||
if v.platform == "linux" {
|
||||
var effective, permitted, inheritable, ambient bool
|
||||
|
@ -303,29 +323,33 @@ func (v *Validator) CheckCapabilities() (msgs []string) {
|
|||
|
||||
for capability, owns := range caps {
|
||||
if err := CapValid(capability, v.HostSpecific); err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("capability %q is not valid, man capabilities(7)", capability))
|
||||
errs = multierror.Append(errs, fmt.Errorf("capability %q is not valid, man capabilities(7)", capability))
|
||||
}
|
||||
|
||||
effective, permitted, ambient, inheritable = false, false, false, false
|
||||
for _, set := range owns {
|
||||
if set == "effective" {
|
||||
effective = true
|
||||
continue
|
||||
}
|
||||
if set == "inheritable" {
|
||||
inheritable = true
|
||||
continue
|
||||
}
|
||||
if set == "permitted" {
|
||||
permitted = true
|
||||
continue
|
||||
}
|
||||
if set == "ambient" {
|
||||
ambient = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if effective && !permitted {
|
||||
msgs = append(msgs, fmt.Sprintf("effective capability %q is not allowed, as it's not permitted", capability))
|
||||
errs = multierror.Append(errs, fmt.Errorf("effective capability %q is not allowed, as it's not permitted", capability))
|
||||
}
|
||||
if ambient && !(effective && inheritable) {
|
||||
msgs = append(msgs, fmt.Sprintf("ambient capability %q is not allowed, as it's not permitted and inheribate", capability))
|
||||
errs = multierror.Append(errs, fmt.Errorf("ambient capability %q is not allowed, as it's not permitted and inheribate", capability))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -336,15 +360,15 @@ func (v *Validator) CheckCapabilities() (msgs []string) {
|
|||
}
|
||||
|
||||
// CheckRlimits checks v.spec.Process.Rlimits
|
||||
func (v *Validator) CheckRlimits() (msgs []string) {
|
||||
func (v *Validator) CheckRlimits() (errs error) {
|
||||
process := v.spec.Process
|
||||
for index, rlimit := range process.Rlimits {
|
||||
for i := index + 1; i < len(process.Rlimits); i++ {
|
||||
if process.Rlimits[index].Type == process.Rlimits[i].Type {
|
||||
msgs = append(msgs, fmt.Sprintf("rlimit can not contain the same type %q.", process.Rlimits[index].Type))
|
||||
errs = multierror.Append(errs, fmt.Errorf("rlimit can not contain the same type %q", process.Rlimits[index].Type))
|
||||
}
|
||||
}
|
||||
msgs = append(msgs, v.rlimitValid(rlimit)...)
|
||||
errs = multierror.Append(errs, v.rlimitValid(rlimit))
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -392,24 +416,49 @@ func supportedMountTypes(OS string, hostSpecific bool) (map[string]bool, error)
|
|||
}
|
||||
|
||||
// CheckMounts checks v.spec.Mounts
|
||||
func (v *Validator) CheckMounts() (msgs []string) {
|
||||
func (v *Validator) CheckMounts() (errs error) {
|
||||
logrus.Debugf("check mounts")
|
||||
|
||||
supportedTypes, err := supportedMountTypes(v.platform, v.HostSpecific)
|
||||
if err != nil {
|
||||
msgs = append(msgs, err.Error())
|
||||
errs = multierror.Append(errs, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, mount := range v.spec.Mounts {
|
||||
if supportedTypes != nil {
|
||||
if !supportedTypes[mount.Type] {
|
||||
msgs = append(msgs, fmt.Sprintf("Unsupported mount type %q", mount.Type))
|
||||
for i, mountA := range v.spec.Mounts {
|
||||
if supportedTypes != nil && !supportedTypes[mountA.Type] {
|
||||
errs = multierror.Append(errs, fmt.Errorf("unsupported mount type %q", mountA.Type))
|
||||
}
|
||||
if v.platform == "windows" {
|
||||
if err := pathValid(v.platform, mountA.Destination); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
if err := pathValid(v.platform, mountA.Source); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
} else {
|
||||
if err := pathValid(v.platform, mountA.Destination); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(mount.Destination) {
|
||||
msgs = append(msgs, fmt.Sprintf("destination %v is not an absolute path", mount.Destination))
|
||||
for j, mountB := range v.spec.Mounts {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
// whether B.Desination is nested within A.Destination
|
||||
nested, err := nestedValid(v.platform, mountA.Destination, mountB.Destination)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
continue
|
||||
}
|
||||
if nested {
|
||||
if v.platform == "windows" && i < j {
|
||||
errs = multierror.Append(errs, fmt.Errorf("on Windows, %v nested within %v is forbidden", mountB.Destination, mountA.Destination))
|
||||
}
|
||||
if i > j {
|
||||
logrus.Warnf("%v will be covered by %v", mountB.Destination, mountA.Destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,17 +466,17 @@ func (v *Validator) CheckMounts() (msgs []string) {
|
|||
}
|
||||
|
||||
// CheckPlatform checks v.platform
|
||||
func (v *Validator) CheckPlatform() (msgs []string) {
|
||||
func (v *Validator) CheckPlatform() (errs error) {
|
||||
logrus.Debugf("check platform")
|
||||
|
||||
if v.platform != "linux" && v.platform != "solaris" && v.platform != "windows" {
|
||||
msgs = append(msgs, fmt.Sprintf("platform %q is not supported", v.platform))
|
||||
errs = multierror.Append(errs, fmt.Errorf("platform %q is not supported", v.platform))
|
||||
return
|
||||
}
|
||||
|
||||
if v.platform == "windows" {
|
||||
if v.spec.Windows == nil {
|
||||
msgs = append(msgs, "'windows' MUST be set when platform is `windows`")
|
||||
errs = multierror.Append(errs, errors.New("'windows' MUST be set when platform is `windows`"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -435,14 +484,14 @@ func (v *Validator) CheckPlatform() (msgs []string) {
|
|||
}
|
||||
|
||||
// CheckLinux checks v.spec.Linux
|
||||
func (v *Validator) CheckLinux() (msgs []string) {
|
||||
func (v *Validator) CheckLinux() (errs error) {
|
||||
logrus.Debugf("check linux")
|
||||
|
||||
if v.spec.Linux == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var typeList = map[rspec.LinuxNamespaceType]struct {
|
||||
var nsTypeList = map[rspec.LinuxNamespaceType]struct {
|
||||
num int
|
||||
newExist bool
|
||||
}{
|
||||
|
@ -458,58 +507,142 @@ func (v *Validator) CheckLinux() (msgs []string) {
|
|||
for index := 0; index < len(v.spec.Linux.Namespaces); index++ {
|
||||
ns := v.spec.Linux.Namespaces[index]
|
||||
if !namespaceValid(ns) {
|
||||
msgs = append(msgs, fmt.Sprintf("namespace %v is invalid.", ns))
|
||||
errs = multierror.Append(errs, fmt.Errorf("namespace %v is invalid", ns))
|
||||
}
|
||||
|
||||
tmpItem := typeList[ns.Type]
|
||||
tmpItem := nsTypeList[ns.Type]
|
||||
tmpItem.num = tmpItem.num + 1
|
||||
if tmpItem.num > 1 {
|
||||
msgs = append(msgs, fmt.Sprintf("duplicated namespace %q", ns.Type))
|
||||
errs = multierror.Append(errs, fmt.Errorf("duplicated namespace %q", ns.Type))
|
||||
}
|
||||
|
||||
if len(ns.Path) == 0 {
|
||||
tmpItem.newExist = true
|
||||
}
|
||||
typeList[ns.Type] = tmpItem
|
||||
nsTypeList[ns.Type] = tmpItem
|
||||
}
|
||||
|
||||
if (len(v.spec.Linux.UIDMappings) > 0 || len(v.spec.Linux.GIDMappings) > 0) && !typeList[rspec.UserNamespace].newExist {
|
||||
msgs = append(msgs, "UID/GID mappings requires a new User namespace to be specified as well")
|
||||
if (len(v.spec.Linux.UIDMappings) > 0 || len(v.spec.Linux.GIDMappings) > 0) && !nsTypeList[rspec.UserNamespace].newExist {
|
||||
errs = multierror.Append(errs, errors.New("the UID/GID mappings requires a new User namespace to be specified as well"))
|
||||
} else if len(v.spec.Linux.UIDMappings) > 5 {
|
||||
msgs = append(msgs, "Only 5 UID mappings are allowed (linux kernel restriction).")
|
||||
errs = multierror.Append(errs, errors.New("only 5 UID mappings are allowed (linux kernel restriction)"))
|
||||
} else if len(v.spec.Linux.GIDMappings) > 5 {
|
||||
msgs = append(msgs, "Only 5 GID mappings are allowed (linux kernel restriction).")
|
||||
errs = multierror.Append(errs, errors.New("only 5 GID mappings are allowed (linux kernel restriction)"))
|
||||
}
|
||||
|
||||
for k := range v.spec.Linux.Sysctl {
|
||||
if strings.HasPrefix(k, "net.") && !typeList[rspec.NetworkNamespace].newExist {
|
||||
msgs = append(msgs, fmt.Sprintf("Sysctl %v requires a new Network namespace to be specified as well", k))
|
||||
if strings.HasPrefix(k, "net.") && !nsTypeList[rspec.NetworkNamespace].newExist {
|
||||
errs = multierror.Append(errs, fmt.Errorf("sysctl %v requires a new Network namespace to be specified as well", k))
|
||||
}
|
||||
if strings.HasPrefix(k, "fs.mqueue.") {
|
||||
if !typeList[rspec.MountNamespace].newExist || !typeList[rspec.IPCNamespace].newExist {
|
||||
msgs = append(msgs, fmt.Sprintf("Sysctl %v requires a new IPC namespace and Mount namespace to be specified as well", k))
|
||||
if !nsTypeList[rspec.MountNamespace].newExist || !nsTypeList[rspec.IPCNamespace].newExist {
|
||||
errs = multierror.Append(errs, fmt.Errorf("sysctl %v requires a new IPC namespace and Mount namespace to be specified as well", k))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v.platform == "linux" && !typeList[rspec.UTSNamespace].newExist && v.spec.Hostname != "" {
|
||||
msgs = append(msgs, fmt.Sprintf("On Linux, hostname requires a new UTS namespace to be specified as well"))
|
||||
if v.platform == "linux" && !nsTypeList[rspec.UTSNamespace].newExist && v.spec.Hostname != "" {
|
||||
errs = multierror.Append(errs, fmt.Errorf("on Linux, hostname requires a new UTS namespace to be specified as well"))
|
||||
}
|
||||
|
||||
// Linux devices validation
|
||||
devList := make(map[string]bool)
|
||||
devTypeList := make(map[string]bool)
|
||||
for index := 0; index < len(v.spec.Linux.Devices); index++ {
|
||||
if !deviceValid(v.spec.Linux.Devices[index]) {
|
||||
msgs = append(msgs, fmt.Sprintf("device %v is invalid.", v.spec.Linux.Devices[index]))
|
||||
device := v.spec.Linux.Devices[index]
|
||||
if !deviceValid(device) {
|
||||
errs = multierror.Append(errs, fmt.Errorf("device %v is invalid", device))
|
||||
}
|
||||
|
||||
if _, exists := devList[device.Path]; exists {
|
||||
errs = multierror.Append(errs, fmt.Errorf("device %s is duplicated", device.Path))
|
||||
} else {
|
||||
var rootfsPath string
|
||||
if filepath.IsAbs(v.spec.Root.Path) {
|
||||
rootfsPath = v.spec.Root.Path
|
||||
} else {
|
||||
rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path)
|
||||
}
|
||||
absPath := filepath.Join(rootfsPath, device.Path)
|
||||
fi, err := os.Stat(absPath)
|
||||
if os.IsNotExist(err) {
|
||||
devList[device.Path] = true
|
||||
} else if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
} else {
|
||||
fStat, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
errs = multierror.Append(errs, fmt.Errorf("cannot determine state for device %s", device.Path))
|
||||
continue
|
||||
}
|
||||
var devType string
|
||||
switch fStat.Mode & syscall.S_IFMT {
|
||||
case syscall.S_IFCHR:
|
||||
devType = "c"
|
||||
case syscall.S_IFBLK:
|
||||
devType = "b"
|
||||
case syscall.S_IFIFO:
|
||||
devType = "p"
|
||||
default:
|
||||
devType = "unmatched"
|
||||
}
|
||||
if devType != device.Type || (devType == "c" && device.Type == "u") {
|
||||
errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path))
|
||||
continue
|
||||
}
|
||||
if devType != "p" {
|
||||
dev := fStat.Rdev
|
||||
major := (dev >> 8) & 0xfff
|
||||
minor := (dev & 0xff) | ((dev >> 12) & 0xfff00)
|
||||
if int64(major) != device.Major || int64(minor) != device.Minor {
|
||||
errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path))
|
||||
continue
|
||||
}
|
||||
}
|
||||
if device.FileMode != nil {
|
||||
expectedPerm := *device.FileMode & os.ModePerm
|
||||
actualPerm := fi.Mode() & os.ModePerm
|
||||
if expectedPerm != actualPerm {
|
||||
errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path))
|
||||
continue
|
||||
}
|
||||
}
|
||||
if device.UID != nil {
|
||||
if *device.UID != fStat.Uid {
|
||||
errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path))
|
||||
continue
|
||||
}
|
||||
}
|
||||
if device.GID != nil {
|
||||
if *device.GID != fStat.Gid {
|
||||
errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unify u->c when comparing, they are synonyms
|
||||
var devID string
|
||||
if device.Type == "u" {
|
||||
devID = fmt.Sprintf("%s:%d:%d", "c", device.Major, device.Minor)
|
||||
} else {
|
||||
devID = fmt.Sprintf("%s:%d:%d", device.Type, device.Major, device.Minor)
|
||||
}
|
||||
|
||||
if _, exists := devTypeList[devID]; exists {
|
||||
logrus.Warnf("type:%s, major:%d and minor:%d for linux devices is duplicated", device.Type, device.Major, device.Minor)
|
||||
} else {
|
||||
devTypeList[devID] = true
|
||||
}
|
||||
}
|
||||
|
||||
if v.spec.Linux.Resources != nil {
|
||||
ms := v.CheckLinuxResources()
|
||||
msgs = append(msgs, ms...)
|
||||
errs = multierror.Append(errs, v.CheckLinuxResources())
|
||||
}
|
||||
|
||||
if v.spec.Linux.Seccomp != nil {
|
||||
ms := v.CheckSeccomp()
|
||||
msgs = append(msgs, ms...)
|
||||
errs = multierror.Append(errs, v.CheckSeccomp())
|
||||
}
|
||||
|
||||
switch v.spec.Linux.RootfsPropagation {
|
||||
|
@ -523,18 +656,18 @@ func (v *Validator) CheckLinux() (msgs []string) {
|
|||
case "unbindable":
|
||||
case "runbindable":
|
||||
default:
|
||||
msgs = append(msgs, "rootfsPropagation must be empty or one of \"private|rprivate|slave|rslave|shared|rshared|unbindable|runbindable\"")
|
||||
errs = multierror.Append(errs, errors.New("rootfsPropagation must be empty or one of \"private|rprivate|slave|rslave|shared|rshared|unbindable|runbindable\""))
|
||||
}
|
||||
|
||||
for _, maskedPath := range v.spec.Linux.MaskedPaths {
|
||||
if !strings.HasPrefix(maskedPath, "/") {
|
||||
msgs = append(msgs, fmt.Sprintf("maskedPath %v is not an absolute path", maskedPath))
|
||||
errs = multierror.Append(errs, fmt.Errorf("maskedPath %v is not an absolute path", maskedPath))
|
||||
}
|
||||
}
|
||||
|
||||
for _, readonlyPath := range v.spec.Linux.ReadonlyPaths {
|
||||
if !strings.HasPrefix(readonlyPath, "/") {
|
||||
msgs = append(msgs, fmt.Sprintf("readonlyPath %v is not an absolute path", readonlyPath))
|
||||
errs = multierror.Append(errs, fmt.Errorf("readonlyPath %v is not an absolute path", readonlyPath))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -542,23 +675,23 @@ func (v *Validator) CheckLinux() (msgs []string) {
|
|||
}
|
||||
|
||||
// CheckLinuxResources checks v.spec.Linux.Resources
|
||||
func (v *Validator) CheckLinuxResources() (msgs []string) {
|
||||
func (v *Validator) CheckLinuxResources() (errs error) {
|
||||
logrus.Debugf("check linux resources")
|
||||
|
||||
r := v.spec.Linux.Resources
|
||||
if r.Memory != nil {
|
||||
if r.Memory.Limit != nil && r.Memory.Swap != nil && uint64(*r.Memory.Limit) > uint64(*r.Memory.Swap) {
|
||||
msgs = append(msgs, fmt.Sprintf("Minimum memoryswap should be larger than memory limit"))
|
||||
errs = multierror.Append(errs, fmt.Errorf("minimum memoryswap should be larger than memory limit"))
|
||||
}
|
||||
if r.Memory.Limit != nil && r.Memory.Reservation != nil && uint64(*r.Memory.Reservation) > uint64(*r.Memory.Limit) {
|
||||
msgs = append(msgs, fmt.Sprintf("Minimum memory limit should be larger than memory reservation"))
|
||||
errs = multierror.Append(errs, fmt.Errorf("minimum memory limit should be larger than memory reservation"))
|
||||
}
|
||||
}
|
||||
if r.Network != nil && v.HostSpecific {
|
||||
var exist bool
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
msgs = append(msgs, err.Error())
|
||||
errs = multierror.Append(errs, err)
|
||||
return
|
||||
}
|
||||
for _, prio := range r.Network.Priorities {
|
||||
|
@ -570,7 +703,24 @@ func (v *Validator) CheckLinuxResources() (msgs []string) {
|
|||
}
|
||||
}
|
||||
if !exist {
|
||||
msgs = append(msgs, fmt.Sprintf("Interface %s does not exist currently", prio.Name))
|
||||
errs = multierror.Append(errs, fmt.Errorf("interface %s does not exist currently", prio.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
for index := 0; index < len(r.Devices); index++ {
|
||||
switch r.Devices[index].Type {
|
||||
case "a", "b", "c":
|
||||
default:
|
||||
errs = multierror.Append(errs, fmt.Errorf("type of devices %s is invalid", r.Devices[index].Type))
|
||||
}
|
||||
|
||||
access := []byte(r.Devices[index].Access)
|
||||
for i := 0; i < len(access); i++ {
|
||||
switch access[i] {
|
||||
case 'r', 'w', 'm':
|
||||
default:
|
||||
errs = multierror.Append(errs, fmt.Errorf("access %s is invalid", r.Devices[index].Access))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -579,16 +729,16 @@ func (v *Validator) CheckLinuxResources() (msgs []string) {
|
|||
}
|
||||
|
||||
// CheckSeccomp checkc v.spec.Linux.Seccomp
|
||||
func (v *Validator) CheckSeccomp() (msgs []string) {
|
||||
func (v *Validator) CheckSeccomp() (errs error) {
|
||||
logrus.Debugf("check linux seccomp")
|
||||
|
||||
s := v.spec.Linux.Seccomp
|
||||
if !seccompActionValid(s.DefaultAction) {
|
||||
msgs = append(msgs, fmt.Sprintf("seccomp defaultAction %q is invalid.", s.DefaultAction))
|
||||
errs = multierror.Append(errs, fmt.Errorf("seccomp defaultAction %q is invalid", s.DefaultAction))
|
||||
}
|
||||
for index := 0; index < len(s.Syscalls); index++ {
|
||||
if !syscallValid(s.Syscalls[index]) {
|
||||
msgs = append(msgs, fmt.Sprintf("syscall %v is invalid.", s.Syscalls[index]))
|
||||
errs = multierror.Append(errs, fmt.Errorf("syscall %v is invalid", s.Syscalls[index]))
|
||||
}
|
||||
}
|
||||
for index := 0; index < len(s.Architectures); index++ {
|
||||
|
@ -612,7 +762,7 @@ func (v *Validator) CheckSeccomp() (msgs []string) {
|
|||
case rspec.ArchPARISC:
|
||||
case rspec.ArchPARISC64:
|
||||
default:
|
||||
msgs = append(msgs, fmt.Sprintf("seccomp architecture %q is invalid", s.Architectures[index]))
|
||||
errs = multierror.Append(errs, fmt.Errorf("seccomp architecture %q is invalid", s.Architectures[index]))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -629,7 +779,7 @@ func CapValid(c string, hostSpecific bool) error {
|
|||
for _, cap := range capability.List() {
|
||||
if c == fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String())) {
|
||||
if hostSpecific && cap > LastCap() {
|
||||
return fmt.Errorf("CAP_%s is not supported on the current host", c)
|
||||
return fmt.Errorf("%s is not supported on the current host", c)
|
||||
}
|
||||
isValid = true
|
||||
break
|
||||
|
@ -637,7 +787,7 @@ func CapValid(c string, hostSpecific bool) error {
|
|||
}
|
||||
|
||||
if !isValid {
|
||||
return fmt.Errorf("Invalid capability: %s", c)
|
||||
return fmt.Errorf("invalid capability: %s", c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -669,9 +819,9 @@ func envValid(env string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (v *Validator) rlimitValid(rlimit rspec.POSIXRlimit) (msgs []string) {
|
||||
func (v *Validator) rlimitValid(rlimit rspec.POSIXRlimit) (errs error) {
|
||||
if rlimit.Hard < rlimit.Soft {
|
||||
msgs = append(msgs, fmt.Sprintf("hard limit of rlimit %s should not be less than soft limit", rlimit.Type))
|
||||
errs = multierror.Append(errs, fmt.Errorf("hard limit of rlimit %s should not be less than soft limit", rlimit.Type))
|
||||
}
|
||||
|
||||
if v.platform == "linux" {
|
||||
|
@ -680,7 +830,7 @@ func (v *Validator) rlimitValid(rlimit rspec.POSIXRlimit) (msgs []string) {
|
|||
return
|
||||
}
|
||||
}
|
||||
msgs = append(msgs, fmt.Sprintf("rlimit type %q is invalid", rlimit.Type))
|
||||
errs = multierror.Append(errs, fmt.Errorf("rlimit type %q is invalid", rlimit.Type))
|
||||
} else {
|
||||
logrus.Warnf("process.rlimits validation not yet implemented for platform %q", v.platform)
|
||||
}
|
||||
|
@ -708,6 +858,65 @@ func namespaceValid(ns rspec.LinuxNamespace) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func pathValid(os, path string) error {
|
||||
if os == "windows" {
|
||||
matched, err := regexp.MatchString("^[a-zA-Z]:(\\\\[^\\\\/<>|:*?\"]+)+$", path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !matched {
|
||||
return fmt.Errorf("invalid windows path %v", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !filepath.IsAbs(path) {
|
||||
return fmt.Errorf("%v is not an absolute path", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check whether pathB is nested whithin pathA
|
||||
func nestedValid(os, pathA, pathB string) (bool, error) {
|
||||
if pathA == pathB {
|
||||
return false, nil
|
||||
}
|
||||
if pathA == "/" && pathB != "" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var sep string
|
||||
if os == "windows" {
|
||||
sep = "\\"
|
||||
} else {
|
||||
sep = "/"
|
||||
}
|
||||
|
||||
splitedPathA := strings.Split(filepath.Clean(pathA), sep)
|
||||
splitedPathB := strings.Split(filepath.Clean(pathB), sep)
|
||||
lenA := len(splitedPathA)
|
||||
lenB := len(splitedPathB)
|
||||
|
||||
if lenA > lenB {
|
||||
if (lenA - lenB) == 1 {
|
||||
// if pathA is longer but not end with separator
|
||||
if splitedPathA[lenA-1] != "" {
|
||||
return false, nil
|
||||
}
|
||||
splitedPathA = splitedPathA[:lenA-1]
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
for i, partA := range splitedPathA {
|
||||
if partA != splitedPathB[i] {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func deviceValid(d rspec.LinuxDevice) bool {
|
||||
switch d.Type {
|
||||
case "b", "c", "u":
|
||||
|
@ -767,38 +976,38 @@ func isStructPtr(t reflect.Type) bool {
|
|||
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
func checkMandatoryUnit(field reflect.Value, tagField reflect.StructField, parent string) (msgs []string) {
|
||||
func checkMandatoryUnit(field reflect.Value, tagField reflect.StructField, parent string) (errs error) {
|
||||
mandatory := !strings.Contains(tagField.Tag.Get("json"), "omitempty")
|
||||
switch field.Kind() {
|
||||
case reflect.Ptr:
|
||||
if mandatory && field.IsNil() {
|
||||
msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name))
|
||||
errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name))
|
||||
}
|
||||
case reflect.String:
|
||||
if mandatory && (field.Len() == 0) {
|
||||
msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name))
|
||||
errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name))
|
||||
}
|
||||
case reflect.Slice:
|
||||
if mandatory && (field.IsNil() || field.Len() == 0) {
|
||||
msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name))
|
||||
errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name))
|
||||
return
|
||||
}
|
||||
for index := 0; index < field.Len(); index++ {
|
||||
mValue := field.Index(index)
|
||||
if mValue.CanInterface() {
|
||||
msgs = append(msgs, checkMandatory(mValue.Interface())...)
|
||||
errs = multierror.Append(errs, checkMandatory(mValue.Interface()))
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
if mandatory && (field.IsNil() || field.Len() == 0) {
|
||||
msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name))
|
||||
return msgs
|
||||
errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name))
|
||||
return
|
||||
}
|
||||
keys := field.MapKeys()
|
||||
for index := 0; index < len(keys); index++ {
|
||||
mValue := field.MapIndex(keys[index])
|
||||
if mValue.CanInterface() {
|
||||
msgs = append(msgs, checkMandatory(mValue.Interface())...)
|
||||
errs = multierror.Append(errs, checkMandatory(mValue.Interface()))
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
@ -807,7 +1016,7 @@ func checkMandatoryUnit(field reflect.Value, tagField reflect.StructField, paren
|
|||
return
|
||||
}
|
||||
|
||||
func checkMandatory(obj interface{}) (msgs []string) {
|
||||
func checkMandatory(obj interface{}) (errs error) {
|
||||
objT := reflect.TypeOf(obj)
|
||||
objV := reflect.ValueOf(obj)
|
||||
if isStructPtr(objT) {
|
||||
|
@ -821,12 +1030,12 @@ func checkMandatory(obj interface{}) (msgs []string) {
|
|||
t := objT.Field(i).Type
|
||||
if isStructPtr(t) && objV.Field(i).IsNil() {
|
||||
if !strings.Contains(objT.Field(i).Tag.Get("json"), "omitempty") {
|
||||
msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty", objT.Name(), objT.Field(i).Name))
|
||||
errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", objT.Name(), objT.Field(i).Name))
|
||||
}
|
||||
} else if (isStruct(t) || isStructPtr(t)) && objV.Field(i).CanInterface() {
|
||||
msgs = append(msgs, checkMandatory(objV.Field(i).Interface())...)
|
||||
errs = multierror.Append(errs, checkMandatory(objV.Field(i).Interface()))
|
||||
} else {
|
||||
msgs = append(msgs, checkMandatoryUnit(objV.Field(i), objT.Field(i), objT.Name())...)
|
||||
errs = multierror.Append(errs, checkMandatoryUnit(objV.Field(i), objT.Field(i), objT.Name()))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -834,7 +1043,7 @@ func checkMandatory(obj interface{}) (msgs []string) {
|
|||
}
|
||||
|
||||
// CheckMandatoryFields checks mandatory field of container's config file
|
||||
func (v *Validator) CheckMandatoryFields() []string {
|
||||
func (v *Validator) CheckMandatoryFields() error {
|
||||
logrus.Debugf("check mandatory fields")
|
||||
|
||||
return checkMandatory(v.spec)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue