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"
2018-01-04 15:53:55 +00:00
osFilepath "github.com/opencontainers/runtime-tools/filepath"
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"
2018-01-04 15:53:55 +00:00
"github.com/xeipuuv/gojsonschema"
2017-02-01 00:45:59 +00:00
)
const specConfig = "config.json"
var (
2018-01-04 15:53:55 +00:00
// http://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html
posixRlimits = [ ] 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" ,
2018-01-04 15:53:55 +00:00
"RLIMIT_NOFILE" ,
"RLIMIT_STACK" ,
}
// https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/getrlimit.2?h=man-pages-4.13
linuxRlimits = append ( posixRlimits , [ ] string {
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_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" ,
2018-01-04 15:53:55 +00:00
} ... )
configSchemaTemplate = "https://raw.githubusercontent.com/opencontainers/runtime-spec/v%s/schema/config-schema.json"
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
2018-01-04 15:53:55 +00:00
func NewValidator ( spec * rspec . Spec , bundlePath string , hostSpecific bool , platform string ) ( Validator , error ) {
2017-07-20 04:07:01 +00:00
if hostSpecific && platform != runtime . GOOS {
2018-01-04 15:53:55 +00:00
return Validator { } , fmt . Errorf ( "When hostSpecific is set, platform must be same as the host platform" )
2017-07-20 04:07:01 +00:00
}
return Validator {
spec : spec ,
bundlePath : bundlePath ,
HostSpecific : hostSpecific ,
platform : platform ,
2018-01-04 15:53:55 +00:00
} , nil
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 ) {
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 {
2018-01-04 15:53:55 +00:00
return Validator { } , specerror . NewError ( specerror . ConfigInRootBundleDir , 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
}
2018-01-04 15:53:55 +00:00
return NewValidator ( & spec , bundlePath , hostSpecific , platform )
2017-02-01 00:45:59 +00:00
}
2017-04-12 23:12:04 +00:00
// CheckAll checks all parts of runtime bundle
2018-01-04 15:53:55 +00:00
func ( v * Validator ) CheckAll ( ) error {
var errs * multierror . Error
errs = multierror . Append ( errs , v . CheckJSONSchema ( ) )
2017-09-06 11:25:19 +00:00
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 . CheckLinux ( ) )
2018-01-04 15:53:55 +00:00
if v . platform == "linux" || v . platform == "solaris" {
errs = multierror . Append ( errs , v . CheckHooks ( ) )
}
2017-02-01 00:45:59 +00:00
2018-01-04 15:53:55 +00:00
return errs . ErrorOrNil ( )
}
// JSONSchemaURL returns the URL for the JSON Schema specifying the
// configuration format. It consumes configSchemaTemplate, but we
// provide it as a function to isolate consumers from inconsistent
// naming as runtime-spec evolves.
func JSONSchemaURL ( version string ) ( url string , err error ) {
ver , err := semver . Parse ( version )
if err != nil {
return "" , specerror . NewError ( specerror . SpecVersionInSemVer , err , rspec . Version )
}
configRenamedToConfigSchemaVersion , err := semver . Parse ( "1.0.0-rc2" ) // config.json became config-schema.json in 1.0.0-rc2
if ver . Compare ( configRenamedToConfigSchemaVersion ) == - 1 {
return "" , fmt . Errorf ( "unsupported configuration version (older than %s)" , configRenamedToConfigSchemaVersion )
}
return fmt . Sprintf ( configSchemaTemplate , version ) , nil
}
// CheckJSONSchema validates the configuration against the
// runtime-spec JSON Schema, using the version of the schema that
// matches the configuration's declared version.
func ( v * Validator ) CheckJSONSchema ( ) ( errs error ) {
2018-02-12 11:55:38 +00:00
logrus . Debugf ( "check JSON schema" )
2018-01-04 15:53:55 +00:00
url , err := JSONSchemaURL ( v . spec . Version )
if err != nil {
errs = multierror . Append ( errs , err )
return errs
}
schemaLoader := gojsonschema . NewReferenceLoader ( url )
documentLoader := gojsonschema . NewGoLoader ( v . spec )
result , err := gojsonschema . Validate ( schemaLoader , documentLoader )
if err != nil {
errs = multierror . Append ( errs , err )
return errs
}
if ! result . Valid ( ) {
for _ , resultError := range result . Errors ( ) {
errs = multierror . Append ( errs , errors . New ( resultError . String ( ) ) )
}
}
return errs
2017-02-01 00:45:59 +00:00
}
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 ,
2018-01-04 15:53:55 +00:00
specerror . NewError ( specerror . RootOnHyperVNotSet , 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 ,
2018-01-04 15:53:55 +00:00
specerror . NewError ( specerror . RootOnNonHyperVRequired , fmt . Errorf ( "for non-Hyper-V containers, Root must be set" ) , rspec . Version ) )
return
}
if v . platform == "windows" {
matched , err := regexp . MatchString ( ` \\\\[?]\\Volume[ { ][a-fA-F0-9] { 8}-[a-fA-F0-9] { 4}-[a-fA-F0-9] { 4}-[a-fA-F0-9] { 4}-[a-fA-F0-9] { 12}[}]\\ ` , v . spec . Root . Path )
if err != nil {
errs = multierror . Append ( errs , err )
} else if ! matched {
errs = multierror . Append ( errs ,
specerror . NewError ( specerror . RootPathOnWindowsGUID , fmt . Errorf ( "root.path is %q, but it MUST be a volume GUID path when target platform is windows" , v . spec . Root . Path ) , rspec . Version ) )
}
if v . spec . Root . Readonly {
errs = multierror . Append ( errs ,
specerror . NewError ( specerror . RootReadonlyOnWindowsFalse , fmt . Errorf ( "root.readonly field MUST be omitted or false when target platform is windows" ) , 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 ,
2018-01-04 15:53:55 +00:00
specerror . NewError ( specerror . RootPathOnPosixConvention , 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 ,
2018-01-04 15:53:55 +00:00
specerror . NewError ( specerror . RootPathExist , 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 ,
2018-01-04 15:53:55 +00:00
specerror . NewError ( specerror . RootPathExist , 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-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 ,
2018-01-04 15:53:55 +00:00
specerror . NewError ( specerror . SpecVersionInSemVer , 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" )
2018-01-04 15:53:55 +00:00
if v . platform != "linux" && v . platform != "solaris" {
errs = multierror . Append ( errs , fmt . Errorf ( "For %q platform, the configuration structure does not support hooks" , v . platform ) )
return
}
2017-04-12 23:12:04 +00:00
if v . spec . Hooks != nil {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs , v . checkEventHooks ( "prestart" , v . spec . Hooks . Prestart , v . HostSpecific ) )
errs = multierror . Append ( errs , v . checkEventHooks ( "poststart" , v . spec . Hooks . Poststart , v . HostSpecific ) )
errs = multierror . Append ( errs , v . checkEventHooks ( "poststop" , v . spec . Hooks . Poststop , v . HostSpecific ) )
2017-04-12 23:12:04 +00:00
}
2017-02-01 00:45:59 +00:00
return
}
2018-01-04 15:53:55 +00:00
func ( v * Validator ) checkEventHooks ( hookType string , hooks [ ] rspec . Hook , hostSpecific bool ) ( errs error ) {
for i , hook := range hooks {
if ! osFilepath . IsAbs ( v . platform , hook . Path ) {
errs = multierror . Append ( errs ,
specerror . NewError (
specerror . PosixHooksPathAbs ,
fmt . Errorf ( "hooks.%s[%d].path %v: is not absolute path" ,
hookType , i , hook . Path ) ,
rspec . Version ) )
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
2018-01-04 15:53:55 +00:00
if ! osFilepath . IsAbs ( v . platform , process . Cwd ) {
errs = multierror . Append ( errs ,
specerror . NewError (
specerror . ProcCwdAbs ,
fmt . Errorf ( "cwd %q is not an absolute path" , process . Cwd ) ,
rspec . Version ) )
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 {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs ,
specerror . NewError (
specerror . ProcArgsOneEntryRequired ,
fmt . Errorf ( "args must not be empty" ) ,
rspec . Version ) )
2017-04-12 23:12:04 +00:00
} else {
2018-01-04 15:53:55 +00:00
if filepath . IsAbs ( process . Args [ 0 ] ) && v . spec . Root != nil {
2017-04-12 23:12:04 +00:00
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
}
}
}
}
2018-01-04 15:53:55 +00:00
if v . platform == "linux" || v . platform == "solaris" {
errs = multierror . Append ( errs , v . CheckRlimits ( ) )
2017-06-06 07:19:04 +00:00
}
2017-04-12 23:12:04 +00:00
2017-07-20 04:07:01 +00:00
if v . platform == "linux" {
2018-01-04 15:53:55 +00:00
if v . spec . Process . Capabilities != nil {
errs = multierror . Append ( errs , v . CheckCapabilities ( ) )
}
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 ) {
2018-01-04 15:53:55 +00:00
if v . platform != "linux" {
errs = multierror . Append ( errs , fmt . Errorf ( "For %q platform, the configuration structure does not support process.capabilities" , v . platform ) )
return
}
2017-04-12 23:12:04 +00:00
process := v . spec . Process
2018-01-04 15:53:55 +00:00
var effective , permitted , inheritable , ambient bool
caps := make ( map [ string ] [ ] string )
2017-04-12 23:12:04 +00:00
2018-01-04 15:53:55 +00:00
for _ , cap := range process . Capabilities . Bounding {
caps [ cap ] = append ( caps [ cap ] , "bounding" )
}
for _ , cap := range process . Capabilities . Effective {
caps [ cap ] = append ( caps [ cap ] , "effective" )
}
for _ , cap := range process . Capabilities . Inheritable {
caps [ cap ] = append ( caps [ cap ] , "inheritable" )
}
for _ , cap := range process . Capabilities . Permitted {
caps [ cap ] = append ( caps [ cap ] , "permitted" )
}
for _ , cap := range process . Capabilities . Ambient {
caps [ cap ] = append ( caps [ cap ] , "ambient" )
}
for capability , owns := range caps {
if err := CapValid ( capability , v . HostSpecific ) ; err != nil {
errs = multierror . Append ( errs , fmt . Errorf ( "capability %q is not valid, man capabilities(7)" , capability ) )
2017-02-01 00:45:59 +00:00
}
2017-04-12 23:12:04 +00:00
2018-01-04 15:53:55 +00:00
effective , permitted , ambient , inheritable = false , false , false , false
for _ , set := range owns {
if set == "effective" {
effective = true
continue
2017-04-12 23:12:04 +00:00
}
2018-01-04 15:53:55 +00:00
if set == "inheritable" {
inheritable = true
continue
2017-07-20 04:07:01 +00:00
}
2018-01-04 15:53:55 +00:00
if set == "permitted" {
permitted = true
continue
2017-07-20 04:07:01 +00:00
}
2018-01-04 15:53:55 +00:00
if set == "ambient" {
ambient = true
continue
2017-07-20 04:07:01 +00:00
}
2017-02-01 00:45:59 +00:00
}
2018-01-04 15:53:55 +00:00
if effective && ! permitted {
errs = multierror . Append ( errs , fmt . Errorf ( "effective capability %q is not allowed, as it's not permitted" , capability ) )
}
if ambient && ! ( permitted && inheritable ) {
errs = multierror . Append ( errs , fmt . Errorf ( "ambient capability %q is not allowed, as it's not permitted and inheribate" , capability ) )
}
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 ) {
2018-01-04 15:53:55 +00:00
if v . platform != "linux" && v . platform != "solaris" {
errs = multierror . Append ( errs , fmt . Errorf ( "For %q platform, the configuration structure does not support process.rlimits" , v . platform ) )
return
}
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 {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs ,
specerror . NewError (
specerror . PosixProcRlimitsErrorOnDup ,
fmt . Errorf ( "rlimit can not contain the same type %q" ,
process . Rlimits [ index ] . Type ) ,
rspec . Version ) )
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 ) )
}
2018-01-04 15:53:55 +00:00
if ! osFilepath . IsAbs ( v . platform , mountA . Destination ) {
errs = multierror . Append ( errs ,
specerror . NewError (
specerror . MountsDestAbs ,
fmt . Errorf ( "mounts[%d].destination %q is not absolute" ,
i ,
mountA . Destination ) ,
rspec . Version ) )
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
2018-01-04 15:53:55 +00:00
nested , err := osFilepath . IsAncestor ( v . platform , mountA . Destination , mountB . Destination , "." )
2017-09-06 11:25:19 +00:00
if err != nil {
errs = multierror . Append ( errs , err )
continue
}
if nested {
if v . platform == "windows" && i < j {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs ,
specerror . NewError (
specerror . MountsDestOnWindowsNotNested ,
fmt . Errorf ( "on Windows, %v nested within %v is forbidden" ,
mountB . Destination , mountA . Destination ) ,
rspec . Version ) )
2017-09-06 11:25:19 +00:00
}
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 {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs ,
specerror . NewError (
specerror . PlatformSpecConfOnWindowsSet ,
fmt . Errorf ( "'windows' MUST be set when platform is `windows`" ) ,
rspec . Version ) )
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 ]
2018-01-04 15:53:55 +00:00
if ns . Path != "" && ! osFilepath . IsAbs ( v . platform , ns . Path ) {
errs = multierror . Append ( errs , specerror . NewError ( specerror . NSPathAbs , fmt . Errorf ( "namespace.path %q is not an absolute path" , ns . Path ) , rspec . Version ) )
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 {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs , specerror . NewError ( specerror . NSErrorOnDup , fmt . Errorf ( "duplicated namespace %q" , ns . Type ) , rspec . Version ) )
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
}
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 {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs , specerror . NewError ( specerror . DevicesAvailable ,
fmt . Errorf ( "cannot determine state for device %s" , device . Path ) , rspec . Version ) )
2017-09-06 11:25:19 +00:00
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" ) {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs , specerror . NewError ( specerror . DevicesFileNotMatch ,
fmt . Errorf ( "unmatched %s already exists in filesystem" , device . Path ) , rspec . Version ) )
2017-09-06 11:25:19 +00:00
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 {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs , specerror . NewError ( specerror . DevicesFileNotMatch ,
fmt . Errorf ( "unmatched %s already exists in filesystem" , device . Path ) , rspec . Version ) )
2017-09-06 11:25:19 +00:00
continue
}
}
if device . FileMode != nil {
expectedPerm := * device . FileMode & os . ModePerm
actualPerm := fi . Mode ( ) & os . ModePerm
if expectedPerm != actualPerm {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs , specerror . NewError ( specerror . DevicesFileNotMatch ,
fmt . Errorf ( "unmatched %s already exists in filesystem" , device . Path ) , rspec . Version ) )
2017-09-06 11:25:19 +00:00
continue
}
}
if device . UID != nil {
if * device . UID != fStat . Uid {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs , specerror . NewError ( specerror . DevicesFileNotMatch ,
fmt . Errorf ( "unmatched %s already exists in filesystem" , device . Path ) , rspec . Version ) )
2017-09-06 11:25:19 +00:00
continue
}
}
if device . GID != nil {
if * device . GID != fStat . Gid {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs , specerror . NewError ( specerror . DevicesFileNotMatch ,
fmt . Errorf ( "unmatched %s already exists in filesystem" , device . Path ) , rspec . Version ) )
2017-09-06 11:25:19 +00:00
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 {
2018-02-12 11:55:38 +00:00
logrus . Warnf ( "%v" , specerror . NewError ( specerror . DevicesErrorOnDup , fmt . Errorf ( "type:%s, major:%d and minor:%d for linux devices is duplicated" , device . Type , device . Major , device . Minor ) , rspec . Version ) )
2017-09-06 11:25:19 +00:00
} 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
}
for _ , maskedPath := range v . spec . Linux . MaskedPaths {
if ! strings . HasPrefix ( maskedPath , "/" ) {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs ,
specerror . NewError (
specerror . MaskedPathsAbs ,
fmt . Errorf ( "maskedPath %v is not an absolute path" , maskedPath ) ,
rspec . Version ) )
2017-02-01 00:45:59 +00:00
}
}
for _ , readonlyPath := range v . spec . Linux . ReadonlyPaths {
if ! strings . HasPrefix ( readonlyPath , "/" ) {
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs ,
specerror . NewError (
specerror . ReadonlyPathsAbs ,
fmt . Errorf ( "readonlyPath %v is not an absolute path" , readonlyPath ) ,
rspec . Version ) )
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 {
2018-01-04 15:53:55 +00:00
case "a" , "b" , "c" , "" :
2017-09-06 11:25:19 +00:00
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
2018-02-12 11:55:38 +00:00
if r . BlockIO != nil && r . BlockIO . WeightDevice != nil {
for i , weightDevice := range r . BlockIO . WeightDevice {
if weightDevice . Weight == nil && weightDevice . LeafWeight == nil {
errs = multierror . Append ( errs ,
specerror . NewError (
specerror . BlkIOWeightOrLeafWeightExist ,
fmt . Errorf ( "linux.resources.blockIO.weightDevice[%d] specifies neither weight nor leafWeight" , i ) ,
rspec . Version ) )
}
}
}
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" {
2018-01-04 15:53:55 +00:00
for _ , val := range linuxRlimits {
2017-06-06 07:19:04 +00:00
if val == rlimit . Type {
return
}
2017-02-01 00:45:59 +00:00
}
2018-01-04 15:53:55 +00:00
errs = multierror . Append ( errs , specerror . NewError ( specerror . PosixProcRlimitsTypeValueError , fmt . Errorf ( "rlimit type %q may not be valid" , rlimit . Type ) , v . spec . Version ) )
} else if v . platform == "solaris" {
for _ , val := range posixRlimits {
if val == rlimit . Type {
return
}
}
errs = multierror . Append ( errs , specerror . NewError ( specerror . PosixProcRlimitsTypeValueError , fmt . Errorf ( "rlimit type %q may not be valid" , rlimit . Type ) , v . spec . Version ) )
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 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" :
2018-01-04 15:53:55 +00:00
if d . Major != 0 || d . Minor != 0 {
2017-02-01 00:45:59 +00:00
return false
}
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" )
2018-02-12 11:55:38 +00:00
if v . spec == nil {
return fmt . Errorf ( "Spec can't be nil" )
}
2017-02-01 00:45:59 +00:00
return checkMandatory ( v . spec )
}