-
Notifications
You must be signed in to change notification settings - Fork 159
add bundle validate, using reflect to check omitempty #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
667f2e1
1f40975
92bda88
2d277db
73f69bf
bc8cb89
1697953
fee7a5c
607ee90
0ad2df8
50ada03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,395 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "os" | ||
| "path" | ||
| "reflect" | ||
| "regexp" | ||
| "strings" | ||
|
|
||
| "github.com/Sirupsen/logrus" | ||
| "github.com/codegangsta/cli" | ||
| "github.com/opencontainers/specs" | ||
| ) | ||
|
|
||
| var bundleValidateFlags = []cli.Flag{ | ||
| cli.StringFlag{Name: "path", Usage: "path to a bundle"}, | ||
| } | ||
|
|
||
| var ( | ||
| defaultRlimits = []string{ | ||
| "RLIMIT_CPU", | ||
| "RLIMIT_FSIZE", | ||
| "RLIMIT_DATA", | ||
| "RLIMIT_STACK", | ||
| "RLIMIT_CORE", | ||
| "RLIMIT_RSS", | ||
| "RLIMIT_NPROC", | ||
| "RLIMIT_NOFILE", | ||
| "RLIMIT_MEMLOCK", | ||
| "RLIMIT_AS", | ||
| "RLIMIT_LOCKS", | ||
| "RLIMIT_SIGPENDING", | ||
| "RLIMIT_MSGQUEUE", | ||
| "RLIMIT_NICE", | ||
| "RLIMIT_RTPRIO", | ||
| "RLIMIT_RTTIME", | ||
| } | ||
| ) | ||
|
|
||
| var bundleValidateCommand = cli.Command{ | ||
| Name: "bvalidate", | ||
| Usage: "validate a OCI bundle", | ||
| Flags: bundleValidateFlags, | ||
| Action: func(context *cli.Context) { | ||
| inputPath := context.String("path") | ||
| if inputPath == "" { | ||
| logrus.Fatalf("Bundle path shouldn't be empty") | ||
| } | ||
|
|
||
| if _, err := os.Stat(inputPath); err != nil { | ||
| logrus.Fatal(err) | ||
| } | ||
|
|
||
| sf, err := os.Open(path.Join(inputPath, "config.json")) | ||
| if err != nil { | ||
| logrus.Fatal(err) | ||
| } | ||
|
|
||
| defer sf.Close() | ||
|
|
||
| var spec specs.LinuxSpec | ||
| if err = json.NewDecoder(sf).Decode(&spec); err != nil { | ||
| logrus.Fatal(err) | ||
| } else { | ||
| if spec.Platform.OS != "linux" { | ||
| logrus.Fatalf("Operation system '%s' of the bundle is not support yet.", spec.Platform.OS) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/support/supported/ |
||
| } | ||
| } | ||
|
|
||
| rf, err := os.Open(path.Join(inputPath, "runtime.json")) | ||
| if err != nil { | ||
| logrus.Fatal(err) | ||
| } | ||
| defer rf.Close() | ||
|
|
||
| var runtime specs.LinuxRuntimeSpec | ||
| if err = json.NewDecoder(rf).Decode(&runtime); err != nil { | ||
| logrus.Fatal(err) | ||
| } | ||
|
|
||
| rootfsPath := path.Join(inputPath, spec.Root.Path) | ||
| if fi, err := os.Stat(rootfsPath); err != nil { | ||
| logrus.Fatalf("Cannot find the rootfs: %v", rootfsPath) | ||
| } else if !fi.IsDir() { | ||
| logrus.Fatalf("Rootfs :%v is not a directory.", spec.Root.Path) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rootfs: |
||
| } | ||
| bundleValidate(spec, runtime, rootfsPath) | ||
| logrus.Infof("Bundle test succeeded.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/test/validation/ |
||
| }, | ||
| } | ||
|
|
||
| func bundleValidate(spec specs.LinuxSpec, runtime specs.LinuxRuntimeSpec, rootfs string) { | ||
| //Open after 0.3.0 | ||
| //CheckMandatoryField(spec) | ||
| //CheckMandatoryField(runtime) | ||
| CheckSemVer(spec.Version) | ||
| CheckMountPoints(spec.Mounts, runtime.Mounts) | ||
| CheckLinuxSpec(spec, runtime) | ||
| CheckLinuxRuntime(runtime.Linux, rootfs) | ||
| } | ||
|
|
||
| func CheckSemVer(version string) { | ||
| re, _ := regexp.Compile("^(\\d+)?\\.(\\d+)?\\.(\\d+)?$") | ||
| if ok := re.Match([]byte(version)); !ok { | ||
| logrus.Fatalf("%s is not a valid version format, please read 'SemVer v2.0.0'", version) | ||
| } | ||
| } | ||
|
|
||
| func CheckMountPoints(mps []specs.MountPoint, rmps map[string]specs.Mount) { | ||
| for index := 0; index < len(mps); index++ { | ||
| if _, ok := rmps[mps[index].Name]; !ok { | ||
| logrus.Fatalf("%s in config/mount does not exist in runtime/mount", mps[index].Name) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| //Linux only | ||
| func CheckLinuxSpec(spec specs.LinuxSpec, runtime specs.LinuxRuntimeSpec) { | ||
| for index := 0; index < len(spec.Linux.Capabilities); index++ { | ||
| capability := spec.Linux.Capabilities[index] | ||
| if !capValid(capability) { | ||
| logrus.Fatalf("%s is not valid, man capabilities(7)", spec.Linux.Capabilities[index]) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| //Linux only | ||
| func CheckLinuxRuntime(runtime specs.LinuxRuntime, rootfs string) { | ||
| if len(runtime.UIDMappings) > 5 { | ||
| logrus.Fatalf("Only 5 UID mappings are allowed (linux kernel restriction).") | ||
| } | ||
| if len(runtime.GIDMappings) > 5 { | ||
| logrus.Fatalf("Only 5 GID mappings are allowed (linux kernel restriction).") | ||
| } | ||
|
|
||
| for index := 0; index < len(runtime.Rlimits); index++ { | ||
| if !rlimitValid(runtime.Rlimits[index].Type) { | ||
| logrus.Fatalf("Rlimit %s is invalid.", runtime.Rlimits[index]) | ||
| } | ||
| } | ||
|
|
||
| for index := 0; index < len(runtime.Namespaces); index++ { | ||
| if !namespaceValid(runtime.Namespaces[index]) { | ||
| logrus.Fatalf("Namespace %s is invalid.", runtime.Namespaces[index]) | ||
| } | ||
| } | ||
|
|
||
| for index := 0; index < len(runtime.Devices); index++ { | ||
| if !deviceValid(runtime.Devices[index]) { | ||
| logrus.Fatalf("Device %s is invalid.", runtime.Devices[index].Path) | ||
| } | ||
| } | ||
|
|
||
| if len(runtime.ApparmorProfile) > 0 { | ||
| profilePath := path.Join(rootfs, "/etc/apparmor.d", runtime.ApparmorProfile) | ||
| _, err := os.Stat(profilePath) | ||
| if err != nil { | ||
| logrus.Fatal(err) | ||
| } | ||
| } | ||
|
|
||
| switch runtime.RootfsPropagation { | ||
| case "": | ||
| case "private": | ||
| case "rprivate": | ||
| case "slave": | ||
| case "rslave": | ||
| case "shared": | ||
| case "rshared": | ||
| default: | ||
| logrus.Fatalf("rootfs-propagation must be empty or one of private|rprivate|slave|rslave|shared|rshared") | ||
| } | ||
|
|
||
| CheckSeccomp(runtime.Seccomp) | ||
| } | ||
|
|
||
| func CheckSeccomp(s specs.Seccomp) { | ||
| if !seccompActionValid(s.DefaultAction) { | ||
| logrus.Fatalf("Seccomp.DefaultAction is invalid.") | ||
| } | ||
| for index := 0; index < len(s.Syscalls); index++ { | ||
| if s.Syscalls[index] != nil { | ||
| if !syscallValid(*(s.Syscalls[index])) { | ||
| logrus.Fatalf("Syscall action is invalid.") | ||
| } | ||
| } | ||
| } | ||
| for index := 0; index < len(s.Architectures); index++ { | ||
| switch s.Architectures[index] { | ||
| case specs.ArchX86: | ||
| case specs.ArchX86_64: | ||
| case specs.ArchX32: | ||
| case specs.ArchARM: | ||
| case specs.ArchAARCH64: | ||
| case specs.ArchMIPS: | ||
| case specs.ArchMIPS64: | ||
| case specs.ArchMIPS64N32: | ||
| case specs.ArchMIPSEL: | ||
| case specs.ArchMIPSEL64: | ||
| case specs.ArchMIPSEL64N32: | ||
| default: | ||
| logrus.Fatalf("Seccomp.Architecture [%s] is invalid", s.Architectures[index]) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func capValid(capability string) bool { | ||
| for _, val := range defaultCaps { | ||
| if val == capability { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| func rlimitValid(rlimit string) bool { | ||
| for _, val := range defaultRlimits { | ||
| if val == rlimit { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| func namespaceValid(ns specs.Namespace) bool { | ||
| switch ns.Type { | ||
| case specs.PIDNamespace: | ||
| case specs.NetworkNamespace: | ||
| case specs.MountNamespace: | ||
| case specs.IPCNamespace: | ||
| case specs.UTSNamespace: | ||
| case specs.UserNamespace: | ||
| default: | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| func deviceValid(d specs.Device) bool { | ||
| switch d.Type { | ||
| case 'b': | ||
| case 'c': | ||
| case 'u': | ||
| if d.Major <= 0 { | ||
| return false | ||
| } | ||
| if d.Minor <= 0 { | ||
| return false | ||
| } | ||
| case 'p': | ||
| if d.Major > 0 || d.Minor > 0 { | ||
| return false | ||
| } | ||
| default: | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| func seccompActionValid(secc specs.Action) bool { | ||
| switch secc { | ||
| case "": | ||
| case specs.ActKill: | ||
| case specs.ActTrap: | ||
| case specs.ActErrno: | ||
| case specs.ActTrace: | ||
| case specs.ActAllow: | ||
| default: | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| func syscallValid(s specs.Syscall) bool { | ||
| if !seccompActionValid(s.Action) { | ||
| return false | ||
| } | ||
| for index := 0; index < len(s.Args); index++ { | ||
| arg := *(s.Args[index]) | ||
| switch arg.Op { | ||
| case specs.OpNotEqual: | ||
| case specs.OpLessEqual: | ||
| case specs.OpEqualTo: | ||
| case specs.OpGreaterEqual: | ||
| case specs.OpGreaterThan: | ||
| case specs.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 | ||
| } | ||
|
|
||
| func checkMandatoryUnit(field reflect.Value, tagField reflect.StructField, parent string) ([]string, bool) { | ||
| var msgs []string | ||
| mandatory := !strings.Contains(tagField.Tag.Get("json"), "omitempty") | ||
| switch field.Kind() { | ||
| case reflect.Ptr: | ||
| if mandatory && field.IsNil() == true { | ||
| msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name)) | ||
| return msgs, false | ||
| } | ||
| case reflect.String: | ||
| if mandatory && (field.Len() == 0) { | ||
| msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name)) | ||
| return msgs, false | ||
| } | ||
| case reflect.Slice: | ||
| if mandatory && (field.Len() == 0) { | ||
| msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name)) | ||
| return msgs, false | ||
| } | ||
| valid := true | ||
| for index := 0; index < field.Len(); index++ { | ||
| mValue := field.Index(index) | ||
| if mValue.CanInterface() { | ||
| if ms, ok := checkMandatory(mValue.Interface()); !ok { | ||
| msgs = append(msgs, ms...) | ||
| valid = false | ||
| } | ||
| } | ||
| } | ||
| return msgs, valid | ||
| case reflect.Map: | ||
| if mandatory && ((field.IsNil() == true) || (field.Len() == 0)) { | ||
| msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name)) | ||
| return msgs, false | ||
| } | ||
| valid := true | ||
| keys := field.MapKeys() | ||
| for index := 0; index < len(keys); index++ { | ||
| mValue := field.MapIndex(keys[index]) | ||
| if mValue.CanInterface() { | ||
| if ms, ok := checkMandatory(mValue.Interface()); !ok { | ||
| msgs = append(msgs, ms...) | ||
| valid = false | ||
| } | ||
| } | ||
| } | ||
| return msgs, valid | ||
| default: | ||
| } | ||
|
|
||
| return nil, true | ||
| } | ||
|
|
||
| func checkMandatory(obj interface{}) (msgs []string, valid bool) { | ||
| objT := reflect.TypeOf(obj) | ||
| objV := reflect.ValueOf(obj) | ||
| if isStructPtr(objT) { | ||
| objT = objT.Elem() | ||
| objV = objV.Elem() | ||
| } else if !isStruct(objT) { | ||
| return nil, true | ||
| } | ||
|
|
||
| valid = true | ||
| 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") { | ||
| msgs = append(msgs, fmt.Sprintf("'%s.%s' is should not be empty", objT.Name(), objT.Field(i).Name)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/is should/should |
||
| valid = false | ||
| } | ||
| } else if (isStruct(t) || isStructPtr(t)) && objV.Field(i).CanInterface() { | ||
| if ms, ok := checkMandatory(objV.Field(i).Interface()); !ok { | ||
| msgs = append(msgs, ms...) | ||
| valid = false | ||
| } | ||
| } else { | ||
| if ms, ok := checkMandatoryUnit(objV.Field(i), objT.Field(i), objT.Name()); !ok { | ||
| msgs = append(msgs, ms...) | ||
| valid = false | ||
| } | ||
| } | ||
|
|
||
| } | ||
| return msgs, valid | ||
| } | ||
|
|
||
| func CheckMandatoryField(obj interface{}) { | ||
| if msgs, valid := checkMandatory(obj); !valid { | ||
| logrus.Fatalf("Mandatory information required: %s.", msgs) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mandatory configuration missing: |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let us use validate for this. I have removed existing unused code in #14