2017-02-01 00:45:59 +00:00
package validate
import (
"bufio"
"encoding/json"
2017-09-06 11:25:19 +00:00
"errors"
2017-02-01 00:45:59 +00:00
"fmt"
"io/ioutil"
2017-04-12 23:12:04 +00:00
"net"
2017-02-01 00:45:59 +00:00
"os"
"path/filepath"
"reflect"
2017-09-06 11:25:19 +00:00
"regexp"
2017-07-20 04:07:01 +00:00
"runtime"
2017-02-01 00:45:59 +00:00
"strings"
2017-09-06 11:25:19 +00:00
"syscall"
2017-02-01 00:45:59 +00:00
"unicode"
"unicode/utf8"
"github.com/blang/semver"
2017-09-06 11:25:19 +00:00
"github.com/hashicorp/go-multierror"
2017-02-01 00:45:59 +00:00
rspec "github.com/opencontainers/runtime-spec/specs-go"
2017-08-05 11:40:46 +00:00
"github.com/sirupsen/logrus"
2017-04-12 23:12:04 +00:00
"github.com/syndtr/gocapability/capability"
2017-09-06 11:25:19 +00:00
"github.com/opencontainers/runtime-tools/specerror"
2017-02-01 00:45:59 +00:00
)
const specConfig = "config.json"
var (
defaultRlimits = [ ] string {
2017-04-12 23:12:04 +00:00
"RLIMIT_AS" ,
"RLIMIT_CORE" ,
2017-02-01 00:45:59 +00:00
"RLIMIT_CPU" ,
"RLIMIT_DATA" ,
2017-04-12 23:12:04 +00:00
"RLIMIT_FSIZE" ,
2017-02-01 00:45:59 +00:00
"RLIMIT_LOCKS" ,
2017-04-12 23:12:04 +00:00
"RLIMIT_MEMLOCK" ,
2017-02-01 00:45:59 +00:00
"RLIMIT_MSGQUEUE" ,
"RLIMIT_NICE" ,
2017-04-12 23:12:04 +00:00
"RLIMIT_NOFILE" ,
"RLIMIT_NPROC" ,
"RLIMIT_RSS" ,
2017-02-01 00:45:59 +00:00
"RLIMIT_RTPRIO" ,
"RLIMIT_RTTIME" ,
2017-04-12 23:12:04 +00:00
"RLIMIT_SIGPENDING" ,
"RLIMIT_STACK" ,
2017-02-01 00:45:59 +00:00
}
)
2017-04-12 23:12:04 +00:00
// Validator represents a validator for runtime bundle
2017-02-01 00:45:59 +00:00
type Validator struct {
spec * rspec . Spec
bundlePath string
HostSpecific bool
2017-07-20 04:07:01 +00:00
platform string
2017-02-01 00:45:59 +00:00
}
2017-04-12 23:12:04 +00:00
// NewValidator creates a Validator
2017-07-20 04:07:01 +00:00
func NewValidator ( spec * rspec . Spec , bundlePath string , hostSpecific bool , platform string ) Validator {
if hostSpecific && platform != runtime . GOOS {
platform = runtime . GOOS
}
return Validator {
spec : spec ,
bundlePath : bundlePath ,
HostSpecific : hostSpecific ,
platform : platform ,
}
2017-02-01 00:45:59 +00:00
}
2017-04-12 23:12:04 +00:00
// NewValidatorFromPath creates a Validator with specified bundle path
2017-07-20 04:07:01 +00:00
func NewValidatorFromPath ( bundlePath string , hostSpecific bool , platform string ) ( Validator , error ) {
if hostSpecific && platform != runtime . GOOS {
platform = runtime . GOOS
}
2017-02-01 00:45:59 +00:00
if bundlePath == "" {
2017-09-06 11:25:19 +00:00
return Validator { } , fmt . Errorf ( "bundle path shouldn't be empty" )
2017-02-01 00:45:59 +00:00
}
if _ , err := os . Stat ( bundlePath ) ; err != nil {
return Validator { } , err
}
configPath := filepath . Join ( bundlePath , specConfig )
content , err := ioutil . ReadFile ( configPath )
if err != nil {
2017-09-06 11:25:19 +00:00
return Validator { } , specerror . NewError ( specerror . ConfigFileExistence , err , rspec . Version )
2017-02-01 00:45:59 +00:00
}
if ! utf8 . Valid ( content ) {
return Validator { } , fmt . Errorf ( "%q is not encoded in UTF-8" , configPath )
}
var spec rspec . Spec
if err = json . Unmarshal ( content , & spec ) ; err != nil {
return Validator { } , err
}
2017-07-20 04:07:01 +00:00
return NewValidator ( & spec , bundlePath , hostSpecific , platform ) , nil
2017-02-01 00:45:59 +00:00
}
2017-04-12 23:12:04 +00:00
// CheckAll checks all parts of runtime bundle
2017-09-06 11:25:19 +00:00
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 ( ) )
2017-02-01 00:45:59 +00:00
return
}
2017-08-05 11:40:46 +00:00
// CheckRoot checks status of v.spec.Root
2017-09-06 11:25:19 +00:00
func ( v * Validator ) CheckRoot ( ) ( errs error ) {
2017-08-05 11:40:46 +00:00
logrus . Debugf ( "check root" )
2017-09-06 11:25:19 +00:00
if v . platform == "windows" && v . spec . Windows != nil && v . spec . Windows . HyperV != nil {
2017-08-05 11:40:46 +00:00
if v . spec . Root != nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs ,
specerror . NewError ( specerror . RootOnHyperV , fmt . Errorf ( "for Hyper-V containers, Root must not be set" ) , rspec . Version ) )
2017-08-05 11:40:46 +00:00
return
}
return
} else if v . spec . Root == nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs ,
specerror . NewError ( specerror . RootOnNonHyperV , fmt . Errorf ( "for non-Hyper-V containers, Root must be set" ) , rspec . Version ) )
2017-08-05 11:40:46 +00:00
return
}
2017-02-01 00:45:59 +00:00
2017-04-12 23:12:04 +00:00
absBundlePath , err := filepath . Abs ( v . bundlePath )
if err != nil {
2017-09-06 11:25:19 +00:00
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 ) )
2017-04-12 23:12:04 +00:00
}
2017-02-01 00:45:59 +00:00
var rootfsPath string
2017-04-12 23:12:04 +00:00
var absRootPath string
2017-02-01 00:45:59 +00:00
if filepath . IsAbs ( v . spec . Root . Path ) {
rootfsPath = v . spec . Root . Path
2017-04-12 23:12:04 +00:00
absRootPath = filepath . Clean ( rootfsPath )
2017-02-01 00:45:59 +00:00
} else {
2017-04-12 23:12:04 +00:00
var err error
2017-02-01 00:45:59 +00:00
rootfsPath = filepath . Join ( v . bundlePath , v . spec . Root . Path )
2017-04-12 23:12:04 +00:00
absRootPath , err = filepath . Abs ( rootfsPath )
if err != nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "unable to convert %q to an absolute path" , rootfsPath ) )
return
2017-04-12 23:12:04 +00:00
}
2017-02-01 00:45:59 +00:00
}
if fi , err := os . Stat ( rootfsPath ) ; err != nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs ,
specerror . NewError ( specerror . PathExistence , fmt . Errorf ( "cannot find the root path %q" , rootfsPath ) , rspec . Version ) )
2017-02-01 00:45:59 +00:00
} else if ! fi . IsDir ( ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs ,
specerror . NewError ( specerror . PathExistence , fmt . Errorf ( "root.path %q is not a directory" , rootfsPath ) , rspec . Version ) )
2017-02-01 00:45:59 +00:00
}
2017-04-12 23:12:04 +00:00
rootParent := filepath . Dir ( absRootPath )
if absRootPath == string ( filepath . Separator ) || rootParent != absBundlePath {
2017-09-06 11:25:19 +00:00
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 ) )
2017-04-12 23:12:04 +00:00
}
2017-07-20 04:07:01 +00:00
if v . platform == "windows" {
2017-06-22 17:32:14 +00:00
if v . spec . Root . Readonly {
2017-09-06 11:25:19 +00:00
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 ) )
2017-06-22 17:32:14 +00:00
}
}
2017-02-01 00:45:59 +00:00
2017-06-22 17:32:14 +00:00
return
2017-02-01 00:45:59 +00:00
}
2017-04-12 23:12:04 +00:00
// CheckSemVer checks v.spec.Version
2017-09-06 11:25:19 +00:00
func ( v * Validator ) CheckSemVer ( ) ( errs error ) {
2017-02-01 00:45:59 +00:00
logrus . Debugf ( "check semver" )
version := v . spec . Version
_ , err := semver . Parse ( version )
if err != nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs ,
specerror . NewError ( specerror . SpecVersion , fmt . Errorf ( "%q is not valid SemVer: %s" , version , err . Error ( ) ) , rspec . Version ) )
2017-02-01 00:45:59 +00:00
}
if version != rspec . Version {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "validate currently only handles version %s, but the supplied configuration targets %s" , rspec . Version , version ) )
2017-02-01 00:45:59 +00:00
}
return
}
2017-04-12 23:12:04 +00:00
// CheckHooks check v.spec.Hooks
2017-09-06 11:25:19 +00:00
func ( v * Validator ) CheckHooks ( ) ( errs error ) {
2017-02-01 00:45:59 +00:00
logrus . Debugf ( "check hooks" )
2017-04-12 23:12:04 +00:00
if v . spec . Hooks != nil {
2017-09-06 11:25:19 +00:00
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 ) )
2017-04-12 23:12:04 +00:00
}
2017-02-01 00:45:59 +00:00
return
}
2017-09-06 11:25:19 +00:00
func checkEventHooks ( hookType string , hooks [ ] rspec . Hook , hostSpecific bool ) ( errs error ) {
2017-02-01 00:45:59 +00:00
for _ , hook := range hooks {
if ! filepath . IsAbs ( hook . Path ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "the %s hook %v: is not absolute path" , hookType , hook . Path ) )
2017-02-01 00:45:59 +00:00
}
if hostSpecific {
fi , err := os . Stat ( hook . Path )
if err != nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "cannot find %s hook: %v" , hookType , hook . Path ) )
2017-02-01 00:45:59 +00:00
}
if fi . Mode ( ) & 0111 == 0 {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "the %s hook %v: is not executable" , hookType , hook . Path ) )
2017-02-01 00:45:59 +00:00
}
}
for _ , env := range hook . Env {
if ! envValid ( env ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "env %q for hook %v is in the invalid form" , env , hook . Path ) )
2017-02-01 00:45:59 +00:00
}
}
}
return
}
2017-04-12 23:12:04 +00:00
// CheckProcess checks v.spec.Process
2017-09-06 11:25:19 +00:00
func ( v * Validator ) CheckProcess ( ) ( errs error ) {
2017-02-01 00:45:59 +00:00
logrus . Debugf ( "check process" )
2017-08-05 11:40:46 +00:00
if v . spec . Process == nil {
return
}
2017-02-01 00:45:59 +00:00
process := v . spec . Process
if ! filepath . IsAbs ( process . Cwd ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "cwd %q is not an absolute path" , process . Cwd ) )
2017-02-01 00:45:59 +00:00
}
for _ , env := range process . Env {
if ! envValid ( env ) {
2017-09-06 11:25:19 +00:00
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 ) )
2017-02-01 00:45:59 +00:00
}
}
2017-04-12 23:12:04 +00:00
if len ( process . Args ) == 0 {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "args must not be empty" ) )
2017-04-12 23:12:04 +00:00
} else {
if filepath . IsAbs ( process . Args [ 0 ] ) {
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 , process . Args [ 0 ] )
fileinfo , err := os . Stat ( absPath )
if os . IsNotExist ( err ) {
logrus . Warnf ( "executable %q is not available in rootfs currently" , process . Args [ 0 ] )
} else if err != nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , err )
2017-04-12 23:12:04 +00:00
} else {
m := fileinfo . Mode ( )
if m . IsDir ( ) || m & 0111 == 0 {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "arg %q is not executable" , process . Args [ 0 ] ) )
2017-04-12 23:12:04 +00:00
}
}
}
}
2017-06-06 07:19:04 +00:00
if v . spec . Process . Capabilities != nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , v . CheckCapabilities ( ) )
2017-06-06 07:19:04 +00:00
}
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , v . CheckRlimits ( ) )
2017-04-12 23:12:04 +00:00
2017-07-20 04:07:01 +00:00
if v . platform == "linux" {
2017-04-12 23:12:04 +00:00
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 {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , err )
2017-04-12 23:12:04 +00:00
}
2017-02-01 00:45:59 +00:00
}
}
2017-04-12 23:12:04 +00:00
return
}
2017-05-12 14:33:29 +00:00
// CheckCapabilities checks v.spec.Process.Capabilities
2017-09-06 11:25:19 +00:00
func ( v * Validator ) CheckCapabilities ( ) ( errs error ) {
2017-04-12 23:12:04 +00:00
process := v . spec . Process
2017-07-20 04:07:01 +00:00
if v . platform == "linux" {
var effective , permitted , inheritable , ambient bool
caps := make ( map [ string ] [ ] string )
2017-04-12 23:12:04 +00:00
for _ , cap := range process . Capabilities . Bounding {
2017-07-20 04:07:01 +00:00
caps [ cap ] = append ( caps [ cap ] , "bounding" )
2017-04-12 23:12:04 +00:00
}
for _ , cap := range process . Capabilities . Effective {
2017-07-20 04:07:01 +00:00
caps [ cap ] = append ( caps [ cap ] , "effective" )
2017-04-12 23:12:04 +00:00
}
for _ , cap := range process . Capabilities . Inheritable {
2017-07-20 04:07:01 +00:00
caps [ cap ] = append ( caps [ cap ] , "inheritable" )
2017-04-12 23:12:04 +00:00
}
for _ , cap := range process . Capabilities . Permitted {
2017-07-20 04:07:01 +00:00
caps [ cap ] = append ( caps [ cap ] , "permitted" )
2017-04-12 23:12:04 +00:00
}
for _ , cap := range process . Capabilities . Ambient {
2017-07-20 04:07:01 +00:00
caps [ cap ] = append ( caps [ cap ] , "ambient" )
2017-02-01 00:45:59 +00:00
}
2017-04-12 23:12:04 +00:00
2017-07-20 04:07:01 +00:00
for capability , owns := range caps {
2017-04-12 23:12:04 +00:00
if err := CapValid ( capability , v . HostSpecific ) ; err != nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "capability %q is not valid, man capabilities(7)" , capability ) )
2017-04-12 23:12:04 +00:00
}
2017-07-20 04:07:01 +00:00
effective , permitted , ambient , inheritable = false , false , false , false
for _ , set := range owns {
if set == "effective" {
effective = true
2017-09-06 11:25:19 +00:00
continue
2017-07-20 04:07:01 +00:00
}
if set == "inheritable" {
inheritable = true
2017-09-06 11:25:19 +00:00
continue
2017-07-20 04:07:01 +00:00
}
if set == "permitted" {
permitted = true
2017-09-06 11:25:19 +00:00
continue
2017-07-20 04:07:01 +00:00
}
if set == "ambient" {
ambient = true
2017-09-06 11:25:19 +00:00
continue
2017-07-20 04:07:01 +00:00
}
}
if effective && ! permitted {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "effective capability %q is not allowed, as it's not permitted" , capability ) )
2017-07-20 04:07:01 +00:00
}
if ambient && ! ( effective && inheritable ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "ambient capability %q is not allowed, as it's not permitted and inheribate" , capability ) )
2017-07-20 04:07:01 +00:00
}
2017-02-01 00:45:59 +00:00
}
2017-04-12 23:12:04 +00:00
} else {
2017-07-20 04:07:01 +00:00
logrus . Warnf ( "process.capabilities validation not yet implemented for OS %q" , v . platform )
2017-02-01 00:45:59 +00:00
}
2017-04-12 23:12:04 +00:00
return
}
2017-05-12 14:33:29 +00:00
// CheckRlimits checks v.spec.Process.Rlimits
2017-09-06 11:25:19 +00:00
func ( v * Validator ) CheckRlimits ( ) ( errs error ) {
2017-04-12 23:12:04 +00:00
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 {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "rlimit can not contain the same type %q" , process . Rlimits [ index ] . Type ) )
2017-04-12 23:12:04 +00:00
}
}
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , v . rlimitValid ( rlimit ) )
2017-02-01 00:45:59 +00:00
}
return
}
func supportedMountTypes ( OS string , hostSpecific bool ) ( map [ string ] bool , error ) {
supportedTypes := make ( map [ string ] bool )
if OS != "linux" && OS != "windows" {
logrus . Warnf ( "%v is not supported to check mount type" , OS )
return nil , nil
} else if OS == "windows" {
supportedTypes [ "ntfs" ] = true
return supportedTypes , nil
}
if hostSpecific {
f , err := os . Open ( "/proc/filesystems" )
if err != nil {
return nil , err
}
defer f . Close ( )
s := bufio . NewScanner ( f )
for s . Scan ( ) {
if err := s . Err ( ) ; err != nil {
return supportedTypes , err
}
text := s . Text ( )
parts := strings . Split ( text , "\t" )
if len ( parts ) > 1 {
supportedTypes [ parts [ 1 ] ] = true
} else {
supportedTypes [ parts [ 0 ] ] = true
}
}
supportedTypes [ "bind" ] = true
return supportedTypes , nil
}
logrus . Warn ( "Checking linux mount types without --host-specific is not supported yet" )
return nil , nil
}
2017-04-12 23:12:04 +00:00
// CheckMounts checks v.spec.Mounts
2017-09-06 11:25:19 +00:00
func ( v * Validator ) CheckMounts ( ) ( errs error ) {
2017-02-01 00:45:59 +00:00
logrus . Debugf ( "check mounts" )
2017-07-20 04:07:01 +00:00
supportedTypes , err := supportedMountTypes ( v . platform , v . HostSpecific )
2017-02-01 00:45:59 +00:00
if err != nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , err )
2017-02-01 00:45:59 +00:00
return
}
2017-09-06 11:25:19 +00:00
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 )
2017-02-01 00:45:59 +00:00
}
2017-06-06 07:19:04 +00:00
}
2017-09-06 11:25:19 +00:00
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 )
}
}
2017-02-01 00:45:59 +00:00
}
}
return
}
2017-07-20 04:07:01 +00:00
// CheckPlatform checks v.platform
2017-09-06 11:25:19 +00:00
func ( v * Validator ) CheckPlatform ( ) ( errs error ) {
2017-07-20 04:07:01 +00:00
logrus . Debugf ( "check platform" )
2017-04-12 23:12:04 +00:00
2017-07-20 04:07:01 +00:00
if v . platform != "linux" && v . platform != "solaris" && v . platform != "windows" {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "platform %q is not supported" , v . platform ) )
2017-07-20 04:07:01 +00:00
return
2017-04-12 23:12:04 +00:00
}
2017-07-20 04:07:01 +00:00
if v . platform == "windows" {
if v . spec . Windows == nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , errors . New ( "'windows' MUST be set when platform is `windows`" ) )
2017-04-12 23:12:04 +00:00
}
}
return
}
// CheckLinux checks v.spec.Linux
2017-09-06 11:25:19 +00:00
func ( v * Validator ) CheckLinux ( ) ( errs error ) {
2017-02-01 00:45:59 +00:00
logrus . Debugf ( "check linux" )
2017-08-05 11:40:46 +00:00
if v . spec . Linux == nil {
return
}
2017-09-06 11:25:19 +00:00
var nsTypeList = map [ rspec . LinuxNamespaceType ] struct {
2017-04-12 23:12:04 +00:00
num int
newExist bool
} {
rspec . PIDNamespace : { 0 , false } ,
rspec . NetworkNamespace : { 0 , false } ,
rspec . MountNamespace : { 0 , false } ,
rspec . IPCNamespace : { 0 , false } ,
rspec . UTSNamespace : { 0 , false } ,
rspec . UserNamespace : { 0 , false } ,
rspec . CgroupNamespace : { 0 , false } ,
}
2017-02-01 00:45:59 +00:00
for index := 0 ; index < len ( v . spec . Linux . Namespaces ) ; index ++ {
2017-04-12 23:12:04 +00:00
ns := v . spec . Linux . Namespaces [ index ]
if ! namespaceValid ( ns ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "namespace %v is invalid" , ns ) )
2017-04-12 23:12:04 +00:00
}
2017-09-06 11:25:19 +00:00
tmpItem := nsTypeList [ ns . Type ]
2017-04-12 23:12:04 +00:00
tmpItem . num = tmpItem . num + 1
if tmpItem . num > 1 {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "duplicated namespace %q" , ns . Type ) )
2017-02-01 00:45:59 +00:00
}
2017-04-12 23:12:04 +00:00
if len ( ns . Path ) == 0 {
tmpItem . newExist = true
}
2017-09-06 11:25:19 +00:00
nsTypeList [ ns . Type ] = tmpItem
2017-02-01 00:45:59 +00:00
}
2017-09-06 11:25:19 +00:00
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" ) )
2017-02-01 00:45:59 +00:00
} else if len ( v . spec . Linux . UIDMappings ) > 5 {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , errors . New ( "only 5 UID mappings are allowed (linux kernel restriction)" ) )
2017-02-01 00:45:59 +00:00
} else if len ( v . spec . Linux . GIDMappings ) > 5 {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , errors . New ( "only 5 GID mappings are allowed (linux kernel restriction)" ) )
2017-02-01 00:45:59 +00:00
}
for k := range v . spec . Linux . Sysctl {
2017-09-06 11:25:19 +00:00
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 ) )
2017-02-01 00:45:59 +00:00
}
if strings . HasPrefix ( k , "fs.mqueue." ) {
2017-09-06 11:25:19 +00:00
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 ) )
2017-02-01 00:45:59 +00:00
}
}
}
2017-09-06 11:25:19 +00:00
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" ) )
2017-02-01 00:45:59 +00:00
}
2017-09-06 11:25:19 +00:00
// Linux devices validation
devList := make ( map [ string ] bool )
devTypeList := make ( map [ string ] bool )
2017-02-01 00:45:59 +00:00
for index := 0 ; index < len ( v . spec . Linux . Devices ) ; index ++ {
2017-09-06 11:25:19 +00:00
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
2017-02-01 00:45:59 +00:00
}
}
if v . spec . Linux . Resources != nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , v . CheckLinuxResources ( ) )
2017-02-01 00:45:59 +00:00
}
if v . spec . Linux . Seccomp != nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , v . CheckSeccomp ( ) )
2017-02-01 00:45:59 +00:00
}
switch v . spec . Linux . RootfsPropagation {
case "" :
case "private" :
case "rprivate" :
case "slave" :
case "rslave" :
case "shared" :
case "rshared" :
2017-06-06 07:19:04 +00:00
case "unbindable" :
case "runbindable" :
2017-02-01 00:45:59 +00:00
default :
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , errors . New ( "rootfsPropagation must be empty or one of \"private|rprivate|slave|rslave|shared|rshared|unbindable|runbindable\"" ) )
2017-02-01 00:45:59 +00:00
}
for _ , maskedPath := range v . spec . Linux . MaskedPaths {
if ! strings . HasPrefix ( maskedPath , "/" ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "maskedPath %v is not an absolute path" , maskedPath ) )
2017-02-01 00:45:59 +00:00
}
}
for _ , readonlyPath := range v . spec . Linux . ReadonlyPaths {
if ! strings . HasPrefix ( readonlyPath , "/" ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "readonlyPath %v is not an absolute path" , readonlyPath ) )
2017-02-01 00:45:59 +00:00
}
}
return
}
2017-04-12 23:12:04 +00:00
// CheckLinuxResources checks v.spec.Linux.Resources
2017-09-06 11:25:19 +00:00
func ( v * Validator ) CheckLinuxResources ( ) ( errs error ) {
2017-02-01 00:45:59 +00:00
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 ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "minimum memoryswap should be larger than memory limit" ) )
2017-02-01 00:45:59 +00:00
}
if r . Memory . Limit != nil && r . Memory . Reservation != nil && uint64 ( * r . Memory . Reservation ) > uint64 ( * r . Memory . Limit ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "minimum memory limit should be larger than memory reservation" ) )
2017-02-01 00:45:59 +00:00
}
}
2017-04-12 23:12:04 +00:00
if r . Network != nil && v . HostSpecific {
var exist bool
interfaces , err := net . Interfaces ( )
if err != nil {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , err )
2017-04-12 23:12:04 +00:00
return
}
for _ , prio := range r . Network . Priorities {
exist = false
for _ , ni := range interfaces {
if prio . Name == ni . Name {
exist = true
break
}
}
if ! exist {
2017-09-06 11:25:19 +00:00
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
2017-04-12 23:12:04 +00:00
}
}
}
2017-02-01 00:45:59 +00:00
return
}
2017-04-12 23:12:04 +00:00
// CheckSeccomp checkc v.spec.Linux.Seccomp
2017-09-06 11:25:19 +00:00
func ( v * Validator ) CheckSeccomp ( ) ( errs error ) {
2017-02-01 00:45:59 +00:00
logrus . Debugf ( "check linux seccomp" )
s := v . spec . Linux . Seccomp
if ! seccompActionValid ( s . DefaultAction ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "seccomp defaultAction %q is invalid" , s . DefaultAction ) )
2017-02-01 00:45:59 +00:00
}
for index := 0 ; index < len ( s . Syscalls ) ; index ++ {
if ! syscallValid ( s . Syscalls [ index ] ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "syscall %v is invalid" , s . Syscalls [ index ] ) )
2017-02-01 00:45:59 +00:00
}
}
for index := 0 ; index < len ( s . Architectures ) ; index ++ {
switch s . Architectures [ index ] {
case rspec . ArchX86 :
case rspec . ArchX86_64 :
case rspec . ArchX32 :
case rspec . ArchARM :
case rspec . ArchAARCH64 :
case rspec . ArchMIPS :
case rspec . ArchMIPS64 :
case rspec . ArchMIPS64N32 :
case rspec . ArchMIPSEL :
case rspec . ArchMIPSEL64 :
case rspec . ArchMIPSEL64N32 :
case rspec . ArchPPC :
case rspec . ArchPPC64 :
case rspec . ArchPPC64LE :
case rspec . ArchS390 :
case rspec . ArchS390X :
2017-04-12 23:12:04 +00:00
case rspec . ArchPARISC :
case rspec . ArchPARISC64 :
2017-02-01 00:45:59 +00:00
default :
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "seccomp architecture %q is invalid" , s . Architectures [ index ] ) )
2017-02-01 00:45:59 +00:00
}
}
return
}
2017-04-12 23:12:04 +00:00
// CapValid checks whether a capability is valid
func CapValid ( c string , hostSpecific bool ) error {
isValid := false
if ! strings . HasPrefix ( c , "CAP_" ) {
return fmt . Errorf ( "capability %s must start with CAP_" , c )
}
for _ , cap := range capability . List ( ) {
if c == fmt . Sprintf ( "CAP_%s" , strings . ToUpper ( cap . String ( ) ) ) {
if hostSpecific && cap > LastCap ( ) {
2017-09-06 11:25:19 +00:00
return fmt . Errorf ( "%s is not supported on the current host" , c )
2017-04-12 23:12:04 +00:00
}
isValid = true
break
}
}
if ! isValid {
2017-09-06 11:25:19 +00:00
return fmt . Errorf ( "invalid capability: %s" , c )
2017-04-12 23:12:04 +00:00
}
return nil
}
// LastCap return last cap of system
func LastCap ( ) capability . Cap {
last := capability . CAP_LAST_CAP
// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
if last == capability . Cap ( 63 ) {
last = capability . CAP_BLOCK_SUSPEND
}
return last
}
2017-02-01 00:45:59 +00:00
func envValid ( env string ) bool {
items := strings . Split ( env , "=" )
if len ( items ) < 2 {
return false
}
for i , ch := range strings . TrimSpace ( items [ 0 ] ) {
if ! unicode . IsDigit ( ch ) && ! unicode . IsLetter ( ch ) && ch != '_' {
return false
}
if i == 0 && unicode . IsDigit ( ch ) {
logrus . Warnf ( "Env %v: variable name beginning with digit is not recommended." , env )
}
}
return true
}
2017-09-06 11:25:19 +00:00
func ( v * Validator ) rlimitValid ( rlimit rspec . POSIXRlimit ) ( errs error ) {
2017-04-12 23:12:04 +00:00
if rlimit . Hard < rlimit . Soft {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "hard limit of rlimit %s should not be less than soft limit" , rlimit . Type ) )
2017-02-01 00:45:59 +00:00
}
2017-06-06 07:19:04 +00:00
2017-07-20 04:07:01 +00:00
if v . platform == "linux" {
2017-06-06 07:19:04 +00:00
for _ , val := range defaultRlimits {
if val == rlimit . Type {
return
}
2017-02-01 00:45:59 +00:00
}
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "rlimit type %q is invalid" , rlimit . Type ) )
2017-06-06 07:19:04 +00:00
} else {
2017-07-20 04:07:01 +00:00
logrus . Warnf ( "process.rlimits validation not yet implemented for platform %q" , v . platform )
2017-02-01 00:45:59 +00:00
}
2017-06-06 07:19:04 +00:00
return
2017-02-01 00:45:59 +00:00
}
2017-04-12 23:12:04 +00:00
func namespaceValid ( ns rspec . LinuxNamespace ) bool {
2017-02-01 00:45:59 +00:00
switch ns . Type {
case rspec . PIDNamespace :
case rspec . NetworkNamespace :
case rspec . MountNamespace :
case rspec . IPCNamespace :
case rspec . UTSNamespace :
case rspec . UserNamespace :
case rspec . CgroupNamespace :
default :
return false
}
2017-04-12 23:12:04 +00:00
if ns . Path != "" && ! filepath . IsAbs ( ns . Path ) {
return false
}
2017-02-01 00:45:59 +00:00
return true
}
2017-09-06 11:25:19 +00:00
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
}
2017-04-12 23:12:04 +00:00
func deviceValid ( d rspec . LinuxDevice ) bool {
2017-02-01 00:45:59 +00:00
switch d . Type {
2017-05-12 14:33:29 +00:00
case "b" , "c" , "u" :
if d . Major <= 0 || d . Minor <= 0 {
2017-02-01 00:45:59 +00:00
return false
}
case "p" :
if d . Major > 0 || d . Minor > 0 {
return false
}
default :
return false
}
return true
}
2017-04-12 23:12:04 +00:00
func seccompActionValid ( secc rspec . LinuxSeccompAction ) bool {
2017-02-01 00:45:59 +00:00
switch secc {
case "" :
case rspec . ActKill :
case rspec . ActTrap :
case rspec . ActErrno :
case rspec . ActTrace :
case rspec . ActAllow :
default :
return false
}
return true
}
2017-04-12 23:12:04 +00:00
func syscallValid ( s rspec . LinuxSyscall ) bool {
2017-02-01 00:45:59 +00:00
if ! seccompActionValid ( s . Action ) {
return false
}
for index := 0 ; index < len ( s . Args ) ; index ++ {
arg := s . Args [ index ]
switch arg . Op {
case rspec . OpNotEqual :
case rspec . OpLessThan :
case rspec . OpLessEqual :
case rspec . OpEqualTo :
case rspec . OpGreaterEqual :
case rspec . OpGreaterThan :
case rspec . OpMaskedEqual :
default :
return false
}
}
return true
}
func isStruct ( t reflect . Type ) bool {
return t . Kind ( ) == reflect . Struct
}
func isStructPtr ( t reflect . Type ) bool {
return t . Kind ( ) == reflect . Ptr && t . Elem ( ) . Kind ( ) == reflect . Struct
}
2017-09-06 11:25:19 +00:00
func checkMandatoryUnit ( field reflect . Value , tagField reflect . StructField , parent string ) ( errs error ) {
2017-02-01 00:45:59 +00:00
mandatory := ! strings . Contains ( tagField . Tag . Get ( "json" ) , "omitempty" )
switch field . Kind ( ) {
case reflect . Ptr :
if mandatory && field . IsNil ( ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "'%s.%s' should not be empty" , parent , tagField . Name ) )
2017-02-01 00:45:59 +00:00
}
case reflect . String :
if mandatory && ( field . Len ( ) == 0 ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "'%s.%s' should not be empty" , parent , tagField . Name ) )
2017-02-01 00:45:59 +00:00
}
case reflect . Slice :
if mandatory && ( field . IsNil ( ) || field . Len ( ) == 0 ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "'%s.%s' should not be empty" , parent , tagField . Name ) )
2017-02-01 00:45:59 +00:00
return
}
for index := 0 ; index < field . Len ( ) ; index ++ {
mValue := field . Index ( index )
if mValue . CanInterface ( ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , checkMandatory ( mValue . Interface ( ) ) )
2017-02-01 00:45:59 +00:00
}
}
case reflect . Map :
if mandatory && ( field . IsNil ( ) || field . Len ( ) == 0 ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "'%s.%s' should not be empty" , parent , tagField . Name ) )
return
2017-02-01 00:45:59 +00:00
}
keys := field . MapKeys ( )
for index := 0 ; index < len ( keys ) ; index ++ {
mValue := field . MapIndex ( keys [ index ] )
if mValue . CanInterface ( ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , checkMandatory ( mValue . Interface ( ) ) )
2017-02-01 00:45:59 +00:00
}
}
default :
}
return
}
2017-09-06 11:25:19 +00:00
func checkMandatory ( obj interface { } ) ( errs error ) {
2017-02-01 00:45:59 +00:00
objT := reflect . TypeOf ( obj )
objV := reflect . ValueOf ( obj )
if isStructPtr ( objT ) {
objT = objT . Elem ( )
objV = objV . Elem ( )
} else if ! isStruct ( objT ) {
return
}
for i := 0 ; i < objT . NumField ( ) ; i ++ {
t := objT . Field ( i ) . Type
if isStructPtr ( t ) && objV . Field ( i ) . IsNil ( ) {
if ! strings . Contains ( objT . Field ( i ) . Tag . Get ( "json" ) , "omitempty" ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , fmt . Errorf ( "'%s.%s' should not be empty" , objT . Name ( ) , objT . Field ( i ) . Name ) )
2017-02-01 00:45:59 +00:00
}
} else if ( isStruct ( t ) || isStructPtr ( t ) ) && objV . Field ( i ) . CanInterface ( ) {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , checkMandatory ( objV . Field ( i ) . Interface ( ) ) )
2017-02-01 00:45:59 +00:00
} else {
2017-09-06 11:25:19 +00:00
errs = multierror . Append ( errs , checkMandatoryUnit ( objV . Field ( i ) , objT . Field ( i ) , objT . Name ( ) ) )
2017-02-01 00:45:59 +00:00
}
}
return
}
2017-04-12 23:12:04 +00:00
// CheckMandatoryFields checks mandatory field of container's config file
2017-09-06 11:25:19 +00:00
func ( v * Validator ) CheckMandatoryFields ( ) error {
2017-02-01 00:45:59 +00:00
logrus . Debugf ( "check mandatory fields" )
return checkMandatory ( v . spec )
}