From af0a494251096c759634bfcafaa9074ff3f1b5a6 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 6 Sep 2017 13:25:19 +0200 Subject: [PATCH] container_create: handle cap add/drop ALL Kubelet can send cap add/drop ALL. Handle that in CRI-O as well. Also, this PR is re-vendoring runtime-tools to fix capabilities add to add caps to _all_ caps set **and** fix a shared memory issue (caps set were initialized with the same slice, if one modifies one slice, it's reflected on the other slices, the vendoring fixes this as well) Signed-off-by: Antonio Murdaca --- server/container_create.go | 91 ++-- server/utils.go | 25 + test/testdata/container_config.json | 1 - .../testdata/container_config_by_imageid.json | 1 - test/testdata/container_config_hostport.json | 1 - test/testdata/container_config_logging.json | 1 - .../testdata/container_config_resolvconf.json | 1 - .../container_config_resolvconf_ro.json | 1 - test/testdata/container_config_seccomp.json | 1 - vendor.conf | 4 +- vendor/github.com/hashicorp/errwrap/LICENSE | 353 ++++++++++++++ vendor/github.com/hashicorp/errwrap/README.md | 89 ++++ .../github.com/hashicorp/errwrap/errwrap.go | 169 +++++++ .../hashicorp/go-multierror/LICENSE | 353 ++++++++++++++ .../hashicorp/go-multierror/README.md | 97 ++++ .../hashicorp/go-multierror/append.go | 41 ++ .../hashicorp/go-multierror/flatten.go | 26 ++ .../hashicorp/go-multierror/format.go | 27 ++ .../hashicorp/go-multierror/multierror.go | 51 ++ .../hashicorp/go-multierror/prefix.go | 37 ++ .../runtime-tools/error/error.go | 92 ++++ .../runtime-tools/generate/generate.go | 163 ++++++- .../runtime-tools/specerror/error.go | 170 +++++++ .../runtime-tools/validate/error.go | 110 ----- .../runtime-tools/validate/validate.go | 435 +++++++++++++----- 25 files changed, 2057 insertions(+), 283 deletions(-) create mode 100644 vendor/github.com/hashicorp/errwrap/LICENSE create mode 100644 vendor/github.com/hashicorp/errwrap/README.md create mode 100644 vendor/github.com/hashicorp/errwrap/errwrap.go create mode 100644 vendor/github.com/hashicorp/go-multierror/LICENSE create mode 100644 vendor/github.com/hashicorp/go-multierror/README.md create mode 100644 vendor/github.com/hashicorp/go-multierror/append.go create mode 100644 vendor/github.com/hashicorp/go-multierror/flatten.go create mode 100644 vendor/github.com/hashicorp/go-multierror/format.go create mode 100644 vendor/github.com/hashicorp/go-multierror/multierror.go create mode 100644 vendor/github.com/hashicorp/go-multierror/prefix.go create mode 100644 vendor/github.com/opencontainers/runtime-tools/error/error.go create mode 100644 vendor/github.com/opencontainers/runtime-tools/specerror/error.go delete mode 100644 vendor/github.com/opencontainers/runtime-tools/validate/error.go diff --git a/server/container_create.go b/server/container_create.go index 7b073284..4917514b 100644 --- a/server/container_create.go +++ b/server/container_create.go @@ -484,8 +484,21 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string, } } + var readOnlyRootfs bool + var privileged bool + if containerConfig.GetLinux().GetSecurityContext() != nil { + if containerConfig.GetLinux().GetSecurityContext().Privileged { + privileged = true + } + + if containerConfig.GetLinux().GetSecurityContext().ReadonlyRootfs { + readOnlyRootfs = true + specgen.SetRootReadonly(true) + } + } + // set this container's apparmor profile if it is set by sandbox - if s.appArmorEnabled { + if s.appArmorEnabled && !privileged { appArmorProfileName := s.getAppArmorProfileName(sb.Annotations(), metadata.Name) if appArmorProfileName != "" { // reload default apparmor profile if it is unloaded. @@ -498,20 +511,6 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string, specgen.SetProcessApparmorProfile(appArmorProfileName) } } - var readOnlyRootfs bool - if containerConfig.GetLinux().GetSecurityContext() != nil { - if containerConfig.GetLinux().GetSecurityContext().Privileged { - if !sb.Privileged() { - return nil, fmt.Errorf("no privileged container allowed in sandbox") - } - specgen.SetupPrivileged(true) - } - - if containerConfig.GetLinux().GetSecurityContext().ReadonlyRootfs { - readOnlyRootfs = true - specgen.SetRootReadonly(true) - } - } logPath := containerConfig.LogPath if logPath == "" { @@ -586,33 +585,59 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string, sb.UpdateCgroupParent(parent) capabilities := linux.GetSecurityContext().GetCapabilities() - toCAPPrefixed := func(cap string) string { - if !strings.HasPrefix(strings.ToLower(cap), "cap_") { - return "CAP_" + cap + if privileged { + // this is setting correct capabilities as well for privileged mode + specgen.SetupPrivileged(true) + } else { + toCAPPrefixed := func(cap string) string { + if !strings.HasPrefix(strings.ToLower(cap), "cap_") { + return "CAP_" + strings.ToUpper(cap) + } + return cap } - return cap - } - if capabilities != nil { - addCaps := capabilities.AddCapabilities - if addCaps != nil { - for _, cap := range addCaps { - if err := specgen.AddProcessCapability(toCAPPrefixed(cap)); err != nil { + + // Add/drop all capabilities if "all" is specified, so that + // following individual add/drop could still work. E.g. + // AddCapabilities: []string{"ALL"}, DropCapabilities: []string{"CHOWN"} + // will be all capabilities without `CAP_CHOWN`. + // see https://github.com/kubernetes/kubernetes/issues/51980 + if inStringSlice(capabilities.GetAddCapabilities(), "ALL") { + for _, c := range getOCICapabilitiesList() { + if err := specgen.AddProcessCapability(c); err != nil { + return nil, err + } + } + } + if inStringSlice(capabilities.GetDropCapabilities(), "ALL") { + for _, c := range getOCICapabilitiesList() { + if err := specgen.DropProcessCapability(c); err != nil { return nil, err } } } - dropCaps := capabilities.DropCapabilities - if dropCaps != nil { - for _, cap := range dropCaps { + if capabilities != nil { + for _, cap := range capabilities.GetAddCapabilities() { + if strings.ToUpper(cap) == "ALL" { + continue + } + if err := specgen.AddProcessCapability(toCAPPrefixed(cap)); err != nil { + return nil, err + } + } + + for _, cap := range capabilities.GetDropCapabilities() { + if strings.ToUpper(cap) == "ALL" { + continue + } if err := specgen.DropProcessCapability(toCAPPrefixed(cap)); err != nil { - logrus.Debugf("failed to drop cap %s: %v", toCAPPrefixed(cap), err) + return nil, fmt.Errorf("failed to drop cap %s %v", toCAPPrefixed(cap), err) } } } + specgen.SetProcessSelinuxLabel(sb.ProcessLabel()) } - specgen.SetProcessSelinuxLabel(sb.ProcessLabel()) specgen.SetLinuxMountLabel(sb.MountLabel()) if containerConfig.GetLinux().GetSecurityContext() != nil && @@ -770,8 +795,10 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string, } specgen.AddAnnotation(annotations.Annotations, string(kubeAnnotationsJSON)) - if err = s.setupSeccomp(&specgen, containerName, sb.Annotations()); err != nil { - return nil, err + if !privileged { + if err = s.setupSeccomp(&specgen, containerName, sb.Annotations()); err != nil { + return nil, err + } } metaname := metadata.Name diff --git a/server/utils.go b/server/utils.go index 39aa96a5..26c347f4 100644 --- a/server/utils.go +++ b/server/utils.go @@ -7,6 +7,8 @@ import ( "strings" "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/opencontainers/runtime-tools/validate" + "github.com/syndtr/gocapability/capability" ) const ( @@ -155,3 +157,26 @@ func newPodNetwork(namespace, name, id, netns string) ocicni.PodNetwork { NetNS: netns, } } + +// inStringSlice checks whether a string is inside a string slice. +// Comparison is case insensitive. +func inStringSlice(ss []string, str string) bool { + for _, s := range ss { + if strings.ToLower(s) == strings.ToLower(str) { + return true + } + } + return false +} + +// getOCICapabilitiesList returns a list of all available capabilities. +func getOCICapabilitiesList() []string { + var caps []string + for _, cap := range capability.List() { + if cap > validate.LastCap() { + continue + } + caps = append(caps, "CAP_"+strings.ToUpper(cap.String())) + } + return caps +} diff --git a/test/testdata/container_config.json b/test/testdata/container_config.json index 73064df3..28936dff 100644 --- a/test/testdata/container_config.json +++ b/test/testdata/container_config.json @@ -57,7 +57,6 @@ "setgid" ], "drop_capabilities": [ - "audit_read" ] }, "selinux_options": { diff --git a/test/testdata/container_config_by_imageid.json b/test/testdata/container_config_by_imageid.json index e2d12409..25b2b13a 100644 --- a/test/testdata/container_config_by_imageid.json +++ b/test/testdata/container_config_by_imageid.json @@ -57,7 +57,6 @@ "setgid" ], "drop_capabilities": [ - "audit_read" ] }, "selinux_options": { diff --git a/test/testdata/container_config_hostport.json b/test/testdata/container_config_hostport.json index ea242056..ddc0014e 100644 --- a/test/testdata/container_config_hostport.json +++ b/test/testdata/container_config_hostport.json @@ -59,7 +59,6 @@ "setgid" ], "drop_capabilities": [ - "audit_read" ] }, "selinux_options": { diff --git a/test/testdata/container_config_logging.json b/test/testdata/container_config_logging.json index 1fd53cfb..92772818 100644 --- a/test/testdata/container_config_logging.json +++ b/test/testdata/container_config_logging.json @@ -59,7 +59,6 @@ "setgid" ], "drop_capabilities": [ - "audit_read" ] }, "selinux_options": { diff --git a/test/testdata/container_config_resolvconf.json b/test/testdata/container_config_resolvconf.json index 2ad7c24f..ad22cf81 100644 --- a/test/testdata/container_config_resolvconf.json +++ b/test/testdata/container_config_resolvconf.json @@ -59,7 +59,6 @@ "setgid" ], "drop_capabilities": [ - "audit_read" ] }, "selinux_options": { diff --git a/test/testdata/container_config_resolvconf_ro.json b/test/testdata/container_config_resolvconf_ro.json index 9a9f4ef6..8e866488 100644 --- a/test/testdata/container_config_resolvconf_ro.json +++ b/test/testdata/container_config_resolvconf_ro.json @@ -59,7 +59,6 @@ "setgid" ], "drop_capabilities": [ - "audit_read" ] }, "selinux_options": { diff --git a/test/testdata/container_config_seccomp.json b/test/testdata/container_config_seccomp.json index 4c827122..9054a2c6 100644 --- a/test/testdata/container_config_seccomp.json +++ b/test/testdata/container_config_seccomp.json @@ -59,7 +59,6 @@ "setgid" ], "drop_capabilities": [ - "audit_read" ] }, "selinux_options": { diff --git a/vendor.conf b/vendor.conf index ec2d732b..81b0b766 100644 --- a/vendor.conf +++ b/vendor.conf @@ -13,7 +13,7 @@ github.com/containernetworking/cni v0.4.0 google.golang.org/grpc v1.0.4 https://github.com/grpc/grpc-go github.com/opencontainers/selinux v1.0.0-rc1 github.com/opencontainers/go-digest v1.0.0-rc0 -github.com/opencontainers/runtime-tools 6bcd3b417fd6962ea04dafdbc2c07444e750572d +github.com/opencontainers/runtime-tools d3f7e9e9e631c7e87552d67dc7c86de33c3fb68a github.com/opencontainers/runc 45bde006ca8c90e089894508708bcf0e2cdf9e13 github.com/mrunalp/fileutils master github.com/vishvananda/netlink master @@ -100,3 +100,5 @@ github.com/beorn7/perks 3ac7bf7a47d159a033b107610db8a1b6575507a4 github.com/containerd/cgroups 7a5fdd8330119dc70d850260db8f3594d89d6943 github.com/go-zoo/bone 031b4005dfe248ccba241a0c9de0f9e112fd6b7c github.com/soheilhy/cmux v0.1.3 +github.com/hashicorp/go-multierror 83588e72410abfbe4df460eeb6f30841ae47d4c4 +github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55 diff --git a/vendor/github.com/hashicorp/errwrap/LICENSE b/vendor/github.com/hashicorp/errwrap/LICENSE new file mode 100644 index 00000000..82b4de97 --- /dev/null +++ b/vendor/github.com/hashicorp/errwrap/LICENSE @@ -0,0 +1,353 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/errwrap/README.md b/vendor/github.com/hashicorp/errwrap/README.md new file mode 100644 index 00000000..1c95f597 --- /dev/null +++ b/vendor/github.com/hashicorp/errwrap/README.md @@ -0,0 +1,89 @@ +# errwrap + +`errwrap` is a package for Go that formalizes the pattern of wrapping errors +and checking if an error contains another error. + +There is a common pattern in Go of taking a returned `error` value and +then wrapping it (such as with `fmt.Errorf`) before returning it. The problem +with this pattern is that you completely lose the original `error` structure. + +Arguably the _correct_ approach is that you should make a custom structure +implementing the `error` interface, and have the original error as a field +on that structure, such [as this example](http://golang.org/pkg/os/#PathError). +This is a good approach, but you have to know the entire chain of possible +rewrapping that happens, when you might just care about one. + +`errwrap` formalizes this pattern (it doesn't matter what approach you use +above) by giving a single interface for wrapping errors, checking if a specific +error is wrapped, and extracting that error. + +## Installation and Docs + +Install using `go get github.com/hashicorp/errwrap`. + +Full documentation is available at +http://godoc.org/github.com/hashicorp/errwrap + +## Usage + +#### Basic Usage + +Below is a very basic example of its usage: + +```go +// A function that always returns an error, but wraps it, like a real +// function might. +func tryOpen() error { + _, err := os.Open("/i/dont/exist") + if err != nil { + return errwrap.Wrapf("Doesn't exist: {{err}}", err) + } + + return nil +} + +func main() { + err := tryOpen() + + // We can use the Contains helpers to check if an error contains + // another error. It is safe to do this with a nil error, or with + // an error that doesn't even use the errwrap package. + if errwrap.Contains(err, ErrNotExist) { + // Do something + } + if errwrap.ContainsType(err, new(os.PathError)) { + // Do something + } + + // Or we can use the associated `Get` functions to just extract + // a specific error. This would return nil if that specific error doesn't + // exist. + perr := errwrap.GetType(err, new(os.PathError)) +} +``` + +#### Custom Types + +If you're already making custom types that properly wrap errors, then +you can get all the functionality of `errwraps.Contains` and such by +implementing the `Wrapper` interface with just one function. Example: + +```go +type AppError { + Code ErrorCode + Err error +} + +func (e *AppError) WrappedErrors() []error { + return []error{e.Err} +} +``` + +Now this works: + +```go +err := &AppError{Err: fmt.Errorf("an error")} +if errwrap.ContainsType(err, fmt.Errorf("")) { + // This will work! +} +``` diff --git a/vendor/github.com/hashicorp/errwrap/errwrap.go b/vendor/github.com/hashicorp/errwrap/errwrap.go new file mode 100644 index 00000000..a733bef1 --- /dev/null +++ b/vendor/github.com/hashicorp/errwrap/errwrap.go @@ -0,0 +1,169 @@ +// Package errwrap implements methods to formalize error wrapping in Go. +// +// All of the top-level functions that take an `error` are built to be able +// to take any error, not just wrapped errors. This allows you to use errwrap +// without having to type-check and type-cast everywhere. +package errwrap + +import ( + "errors" + "reflect" + "strings" +) + +// WalkFunc is the callback called for Walk. +type WalkFunc func(error) + +// Wrapper is an interface that can be implemented by custom types to +// have all the Contains, Get, etc. functions in errwrap work. +// +// When Walk reaches a Wrapper, it will call the callback for every +// wrapped error in addition to the wrapper itself. Since all the top-level +// functions in errwrap use Walk, this means that all those functions work +// with your custom type. +type Wrapper interface { + WrappedErrors() []error +} + +// Wrap defines that outer wraps inner, returning an error type that +// can be cleanly used with the other methods in this package, such as +// Contains, GetAll, etc. +// +// This function won't modify the error message at all (the outer message +// will be used). +func Wrap(outer, inner error) error { + return &wrappedError{ + Outer: outer, + Inner: inner, + } +} + +// Wrapf wraps an error with a formatting message. This is similar to using +// `fmt.Errorf` to wrap an error. If you're using `fmt.Errorf` to wrap +// errors, you should replace it with this. +// +// format is the format of the error message. The string '{{err}}' will +// be replaced with the original error message. +func Wrapf(format string, err error) error { + outerMsg := "" + if err != nil { + outerMsg = err.Error() + } + + outer := errors.New(strings.Replace( + format, "{{err}}", outerMsg, -1)) + + return Wrap(outer, err) +} + +// Contains checks if the given error contains an error with the +// message msg. If err is not a wrapped error, this will always return +// false unless the error itself happens to match this msg. +func Contains(err error, msg string) bool { + return len(GetAll(err, msg)) > 0 +} + +// ContainsType checks if the given error contains an error with +// the same concrete type as v. If err is not a wrapped error, this will +// check the err itself. +func ContainsType(err error, v interface{}) bool { + return len(GetAllType(err, v)) > 0 +} + +// Get is the same as GetAll but returns the deepest matching error. +func Get(err error, msg string) error { + es := GetAll(err, msg) + if len(es) > 0 { + return es[len(es)-1] + } + + return nil +} + +// GetType is the same as GetAllType but returns the deepest matching error. +func GetType(err error, v interface{}) error { + es := GetAllType(err, v) + if len(es) > 0 { + return es[len(es)-1] + } + + return nil +} + +// GetAll gets all the errors that might be wrapped in err with the +// given message. The order of the errors is such that the outermost +// matching error (the most recent wrap) is index zero, and so on. +func GetAll(err error, msg string) []error { + var result []error + + Walk(err, func(err error) { + if err.Error() == msg { + result = append(result, err) + } + }) + + return result +} + +// GetAllType gets all the errors that are the same type as v. +// +// The order of the return value is the same as described in GetAll. +func GetAllType(err error, v interface{}) []error { + var result []error + + var search string + if v != nil { + search = reflect.TypeOf(v).String() + } + Walk(err, func(err error) { + var needle string + if err != nil { + needle = reflect.TypeOf(err).String() + } + + if needle == search { + result = append(result, err) + } + }) + + return result +} + +// Walk walks all the wrapped errors in err and calls the callback. If +// err isn't a wrapped error, this will be called once for err. If err +// is a wrapped error, the callback will be called for both the wrapper +// that implements error as well as the wrapped error itself. +func Walk(err error, cb WalkFunc) { + if err == nil { + return + } + + switch e := err.(type) { + case *wrappedError: + cb(e.Outer) + Walk(e.Inner, cb) + case Wrapper: + cb(err) + + for _, err := range e.WrappedErrors() { + Walk(err, cb) + } + default: + cb(err) + } +} + +// wrappedError is an implementation of error that has both the +// outer and inner errors. +type wrappedError struct { + Outer error + Inner error +} + +func (w *wrappedError) Error() string { + return w.Outer.Error() +} + +func (w *wrappedError) WrappedErrors() []error { + return []error{w.Outer, w.Inner} +} diff --git a/vendor/github.com/hashicorp/go-multierror/LICENSE b/vendor/github.com/hashicorp/go-multierror/LICENSE new file mode 100644 index 00000000..82b4de97 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/LICENSE @@ -0,0 +1,353 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/go-multierror/README.md b/vendor/github.com/hashicorp/go-multierror/README.md new file mode 100644 index 00000000..ead5830f --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/README.md @@ -0,0 +1,97 @@ +# go-multierror + +[![Build Status](http://img.shields.io/travis/hashicorp/go-multierror.svg?style=flat-square)][travis] +[![Go Documentation](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godocs] + +[travis]: https://travis-ci.org/hashicorp/go-multierror +[godocs]: https://godoc.org/github.com/hashicorp/go-multierror + +`go-multierror` is a package for Go that provides a mechanism for +representing a list of `error` values as a single `error`. + +This allows a function in Go to return an `error` that might actually +be a list of errors. If the caller knows this, they can unwrap the +list and access the errors. If the caller doesn't know, the error +formats to a nice human-readable format. + +`go-multierror` implements the +[errwrap](https://github.com/hashicorp/errwrap) interface so that it can +be used with that library, as well. + +## Installation and Docs + +Install using `go get github.com/hashicorp/go-multierror`. + +Full documentation is available at +http://godoc.org/github.com/hashicorp/go-multierror + +## Usage + +go-multierror is easy to use and purposely built to be unobtrusive in +existing Go applications/libraries that may not be aware of it. + +**Building a list of errors** + +The `Append` function is used to create a list of errors. This function +behaves a lot like the Go built-in `append` function: it doesn't matter +if the first argument is nil, a `multierror.Error`, or any other `error`, +the function behaves as you would expect. + +```go +var result error + +if err := step1(); err != nil { + result = multierror.Append(result, err) +} +if err := step2(); err != nil { + result = multierror.Append(result, err) +} + +return result +``` + +**Customizing the formatting of the errors** + +By specifying a custom `ErrorFormat`, you can customize the format +of the `Error() string` function: + +```go +var result *multierror.Error + +// ... accumulate errors here, maybe using Append + +if result != nil { + result.ErrorFormat = func([]error) string { + return "errors!" + } +} +``` + +**Accessing the list of errors** + +`multierror.Error` implements `error` so if the caller doesn't know about +multierror, it will work just fine. But if you're aware a multierror might +be returned, you can use type switches to access the list of errors: + +```go +if err := something(); err != nil { + if merr, ok := err.(*multierror.Error); ok { + // Use merr.Errors + } +} +``` + +**Returning a multierror only if there are errors** + +If you build a `multierror.Error`, you can use the `ErrorOrNil` function +to return an `error` implementation only if there are errors to return: + +```go +var result *multierror.Error + +// ... accumulate errors here + +// Return the `error` only if errors were added to the multierror, otherwise +// return nil since there are no errors. +return result.ErrorOrNil() +``` diff --git a/vendor/github.com/hashicorp/go-multierror/append.go b/vendor/github.com/hashicorp/go-multierror/append.go new file mode 100644 index 00000000..775b6e75 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/append.go @@ -0,0 +1,41 @@ +package multierror + +// Append is a helper function that will append more errors +// onto an Error in order to create a larger multi-error. +// +// If err is not a multierror.Error, then it will be turned into +// one. If any of the errs are multierr.Error, they will be flattened +// one level into err. +func Append(err error, errs ...error) *Error { + switch err := err.(type) { + case *Error: + // Typed nils can reach here, so initialize if we are nil + if err == nil { + err = new(Error) + } + + // Go through each error and flatten + for _, e := range errs { + switch e := e.(type) { + case *Error: + if e != nil { + err.Errors = append(err.Errors, e.Errors...) + } + default: + if e != nil { + err.Errors = append(err.Errors, e) + } + } + } + + return err + default: + newErrs := make([]error, 0, len(errs)+1) + if err != nil { + newErrs = append(newErrs, err) + } + newErrs = append(newErrs, errs...) + + return Append(&Error{}, newErrs...) + } +} diff --git a/vendor/github.com/hashicorp/go-multierror/flatten.go b/vendor/github.com/hashicorp/go-multierror/flatten.go new file mode 100644 index 00000000..aab8e9ab --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/flatten.go @@ -0,0 +1,26 @@ +package multierror + +// Flatten flattens the given error, merging any *Errors together into +// a single *Error. +func Flatten(err error) error { + // If it isn't an *Error, just return the error as-is + if _, ok := err.(*Error); !ok { + return err + } + + // Otherwise, make the result and flatten away! + flatErr := new(Error) + flatten(err, flatErr) + return flatErr +} + +func flatten(err error, flatErr *Error) { + switch err := err.(type) { + case *Error: + for _, e := range err.Errors { + flatten(e, flatErr) + } + default: + flatErr.Errors = append(flatErr.Errors, err) + } +} diff --git a/vendor/github.com/hashicorp/go-multierror/format.go b/vendor/github.com/hashicorp/go-multierror/format.go new file mode 100644 index 00000000..6c7a3cc9 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/format.go @@ -0,0 +1,27 @@ +package multierror + +import ( + "fmt" + "strings" +) + +// ErrorFormatFunc is a function callback that is called by Error to +// turn the list of errors into a string. +type ErrorFormatFunc func([]error) string + +// ListFormatFunc is a basic formatter that outputs the number of errors +// that occurred along with a bullet point list of the errors. +func ListFormatFunc(es []error) string { + if len(es) == 1 { + return fmt.Sprintf("1 error occurred:\n\n* %s", es[0]) + } + + points := make([]string, len(es)) + for i, err := range es { + points[i] = fmt.Sprintf("* %s", err) + } + + return fmt.Sprintf( + "%d errors occurred:\n\n%s", + len(es), strings.Join(points, "\n")) +} diff --git a/vendor/github.com/hashicorp/go-multierror/multierror.go b/vendor/github.com/hashicorp/go-multierror/multierror.go new file mode 100644 index 00000000..89b1422d --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/multierror.go @@ -0,0 +1,51 @@ +package multierror + +import ( + "fmt" +) + +// Error is an error type to track multiple errors. This is used to +// accumulate errors in cases and return them as a single "error". +type Error struct { + Errors []error + ErrorFormat ErrorFormatFunc +} + +func (e *Error) Error() string { + fn := e.ErrorFormat + if fn == nil { + fn = ListFormatFunc + } + + return fn(e.Errors) +} + +// ErrorOrNil returns an error interface if this Error represents +// a list of errors, or returns nil if the list of errors is empty. This +// function is useful at the end of accumulation to make sure that the value +// returned represents the existence of errors. +func (e *Error) ErrorOrNil() error { + if e == nil { + return nil + } + if len(e.Errors) == 0 { + return nil + } + + return e +} + +func (e *Error) GoString() string { + return fmt.Sprintf("*%#v", *e) +} + +// WrappedErrors returns the list of errors that this Error is wrapping. +// It is an implementation of the errwrap.Wrapper interface so that +// multierror.Error can be used with that library. +// +// This method is not safe to be called concurrently and is no different +// than accessing the Errors field directly. It is implemented only to +// satisfy the errwrap.Wrapper interface. +func (e *Error) WrappedErrors() []error { + return e.Errors +} diff --git a/vendor/github.com/hashicorp/go-multierror/prefix.go b/vendor/github.com/hashicorp/go-multierror/prefix.go new file mode 100644 index 00000000..5c477abe --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/prefix.go @@ -0,0 +1,37 @@ +package multierror + +import ( + "fmt" + + "github.com/hashicorp/errwrap" +) + +// Prefix is a helper function that will prefix some text +// to the given error. If the error is a multierror.Error, then +// it will be prefixed to each wrapped error. +// +// This is useful to use when appending multiple multierrors +// together in order to give better scoping. +func Prefix(err error, prefix string) error { + if err == nil { + return nil + } + + format := fmt.Sprintf("%s {{err}}", prefix) + switch err := err.(type) { + case *Error: + // Typed nils can reach here, so initialize if we are nil + if err == nil { + err = new(Error) + } + + // Wrap each of the errors + for i, e := range err.Errors { + err.Errors[i] = errwrap.Wrapf(format, e) + } + + return err + default: + return errwrap.Wrapf(format, err) + } +} diff --git a/vendor/github.com/opencontainers/runtime-tools/error/error.go b/vendor/github.com/opencontainers/runtime-tools/error/error.go new file mode 100644 index 00000000..f5a90800 --- /dev/null +++ b/vendor/github.com/opencontainers/runtime-tools/error/error.go @@ -0,0 +1,92 @@ +// Package error implements generic tooling for tracking RFC 2119 +// violations and linking back to the appropriate specification section. +package error + +import ( + "fmt" + "strings" +) + +// Level represents the RFC 2119 compliance levels +type Level int + +const ( + // MAY-level + + // May represents 'MAY' in RFC 2119. + May Level = iota + // Optional represents 'OPTIONAL' in RFC 2119. + Optional + + // SHOULD-level + + // Should represents 'SHOULD' in RFC 2119. + Should + // ShouldNot represents 'SHOULD NOT' in RFC 2119. + ShouldNot + // Recommended represents 'RECOMMENDED' in RFC 2119. + Recommended + // NotRecommended represents 'NOT RECOMMENDED' in RFC 2119. + NotRecommended + + // MUST-level + + // Must represents 'MUST' in RFC 2119 + Must + // MustNot represents 'MUST NOT' in RFC 2119. + MustNot + // Shall represents 'SHALL' in RFC 2119. + Shall + // ShallNot represents 'SHALL NOT' in RFC 2119. + ShallNot + // Required represents 'REQUIRED' in RFC 2119. + Required +) + +// Error represents an error with compliance level and specification reference. +type Error struct { + // Level represents the RFC 2119 compliance level. + Level Level + + // Reference is a URL for the violated specification requirement. + Reference string + + // Err holds additional details about the violation. + Err error +} + +// ParseLevel takes a string level and returns the RFC 2119 compliance level constant. +func ParseLevel(level string) (Level, error) { + switch strings.ToUpper(level) { + case "MAY": + fallthrough + case "OPTIONAL": + return May, nil + case "SHOULD": + fallthrough + case "SHOULDNOT": + fallthrough + case "RECOMMENDED": + fallthrough + case "NOTRECOMMENDED": + return Should, nil + case "MUST": + fallthrough + case "MUSTNOT": + fallthrough + case "SHALL": + fallthrough + case "SHALLNOT": + fallthrough + case "REQUIRED": + return Must, nil + } + + var l Level + return l, fmt.Errorf("%q is not a valid compliance level", level) +} + +// Error returns the error message with specification reference. +func (err *Error) Error() string { + return fmt.Sprintf("%s\nRefer to: %s", err.Err.Error(), err.Reference) +} diff --git a/vendor/github.com/opencontainers/runtime-tools/generate/generate.go b/vendor/github.com/opencontainers/runtime-tools/generate/generate.go index 2cf4a363..fce88f5e 100644 --- a/vendor/github.com/opencontainers/runtime-tools/generate/generate.go +++ b/vendor/github.com/opencontainers/runtime-tools/generate/generate.go @@ -744,6 +744,38 @@ func (g *Generator) ClearPreStartHooks() { func (g *Generator) AddPreStartHook(path string, args []string) { g.initSpecHooks() hook := rspec.Hook{Path: path, Args: args} + for i, hook := range g.spec.Hooks.Prestart { + if hook.Path == path { + g.spec.Hooks.Prestart[i] = hook + return + } + } + g.spec.Hooks.Prestart = append(g.spec.Hooks.Prestart, hook) +} + +// AddPreStartHookEnv adds envs of a prestart hook into g.spec.Hooks.Prestart. +func (g *Generator) AddPreStartHookEnv(path string, envs []string) { + g.initSpecHooks() + for i, hook := range g.spec.Hooks.Prestart { + if hook.Path == path { + g.spec.Hooks.Prestart[i].Env = envs + return + } + } + hook := rspec.Hook{Path: path, Env: envs} + g.spec.Hooks.Prestart = append(g.spec.Hooks.Prestart, hook) +} + +// AddPreStartHookTimeout adds timeout of a prestart hook into g.spec.Hooks.Prestart. +func (g *Generator) AddPreStartHookTimeout(path string, timeout int) { + g.initSpecHooks() + for i, hook := range g.spec.Hooks.Prestart { + if hook.Path == path { + g.spec.Hooks.Prestart[i].Timeout = &timeout + return + } + } + hook := rspec.Hook{Path: path, Timeout: &timeout} g.spec.Hooks.Prestart = append(g.spec.Hooks.Prestart, hook) } @@ -762,6 +794,38 @@ func (g *Generator) ClearPostStopHooks() { func (g *Generator) AddPostStopHook(path string, args []string) { g.initSpecHooks() hook := rspec.Hook{Path: path, Args: args} + for i, hook := range g.spec.Hooks.Poststop { + if hook.Path == path { + g.spec.Hooks.Poststop[i] = hook + return + } + } + g.spec.Hooks.Poststop = append(g.spec.Hooks.Poststop, hook) +} + +// AddPostStopHookEnv adds envs of a poststop hook into g.spec.Hooks.Poststop. +func (g *Generator) AddPostStopHookEnv(path string, envs []string) { + g.initSpecHooks() + for i, hook := range g.spec.Hooks.Poststop { + if hook.Path == path { + g.spec.Hooks.Poststop[i].Env = envs + return + } + } + hook := rspec.Hook{Path: path, Env: envs} + g.spec.Hooks.Poststop = append(g.spec.Hooks.Poststop, hook) +} + +// AddPostStopHookTimeout adds timeout of a poststop hook into g.spec.Hooks.Poststop. +func (g *Generator) AddPostStopHookTimeout(path string, timeout int) { + g.initSpecHooks() + for i, hook := range g.spec.Hooks.Poststop { + if hook.Path == path { + g.spec.Hooks.Poststop[i].Timeout = &timeout + return + } + } + hook := rspec.Hook{Path: path, Timeout: &timeout} g.spec.Hooks.Poststop = append(g.spec.Hooks.Poststop, hook) } @@ -780,6 +844,38 @@ func (g *Generator) ClearPostStartHooks() { func (g *Generator) AddPostStartHook(path string, args []string) { g.initSpecHooks() hook := rspec.Hook{Path: path, Args: args} + for i, hook := range g.spec.Hooks.Poststart { + if hook.Path == path { + g.spec.Hooks.Poststart[i] = hook + return + } + } + g.spec.Hooks.Poststart = append(g.spec.Hooks.Poststart, hook) +} + +// AddPostStartHookEnv adds envs of a poststart hook into g.spec.Hooks.Poststart. +func (g *Generator) AddPostStartHookEnv(path string, envs []string) { + g.initSpecHooks() + for i, hook := range g.spec.Hooks.Poststart { + if hook.Path == path { + g.spec.Hooks.Poststart[i].Env = envs + return + } + } + hook := rspec.Hook{Path: path, Env: envs} + g.spec.Hooks.Poststart = append(g.spec.Hooks.Poststart, hook) +} + +// AddPostStartHookTimeout adds timeout of a poststart hook into g.spec.Hooks.Poststart. +func (g *Generator) AddPostStartHookTimeout(path string, timeout int) { + g.initSpecHooks() + for i, hook := range g.spec.Hooks.Poststart { + if hook.Path == path { + g.spec.Hooks.Poststart[i].Timeout = &timeout + return + } + } + hook := rspec.Hook{Path: path, Timeout: &timeout} g.spec.Hooks.Poststart = append(g.spec.Hooks.Poststart, hook) } @@ -860,11 +956,12 @@ func (g *Generator) SetupPrivileged(privileged bool) { } g.initSpecLinux() g.initSpecProcessCapabilities() - g.spec.Process.Capabilities.Bounding = finalCapList - g.spec.Process.Capabilities.Effective = finalCapList - g.spec.Process.Capabilities.Inheritable = finalCapList - g.spec.Process.Capabilities.Permitted = finalCapList - g.spec.Process.Capabilities.Ambient = finalCapList + g.ClearProcessCapabilities() + g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding, finalCapList...) + g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective, finalCapList...) + g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable, finalCapList...) + g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted, finalCapList...) + g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient, finalCapList...) g.spec.Process.SelinuxLabel = "" g.spec.Process.ApparmorProfile = "" g.spec.Linux.Seccomp = nil @@ -892,40 +989,60 @@ func (g *Generator) AddProcessCapability(c string) error { g.initSpecProcessCapabilities() + var foundBounding bool for _, cap := range g.spec.Process.Capabilities.Bounding { if strings.ToUpper(cap) == cp { - return nil + foundBounding = true + break } } - g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding, cp) + if !foundBounding { + g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding, cp) + } + var foundEffective bool for _, cap := range g.spec.Process.Capabilities.Effective { if strings.ToUpper(cap) == cp { - return nil + foundEffective = true + break } } - g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective, cp) + if !foundEffective { + g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective, cp) + } + var foundInheritable bool for _, cap := range g.spec.Process.Capabilities.Inheritable { if strings.ToUpper(cap) == cp { - return nil + foundInheritable = true + break } } - g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable, cp) + if !foundInheritable { + g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable, cp) + } + var foundPermitted bool for _, cap := range g.spec.Process.Capabilities.Permitted { if strings.ToUpper(cap) == cp { - return nil + foundPermitted = true + break } } - g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted, cp) + if !foundPermitted { + g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted, cp) + } + var foundAmbient bool for _, cap := range g.spec.Process.Capabilities.Ambient { if strings.ToUpper(cap) == cp { - return nil + foundAmbient = true + break } } - g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient, cp) + if !foundAmbient { + g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient, cp) + } return nil } @@ -939,33 +1056,39 @@ func (g *Generator) DropProcessCapability(c string) error { g.initSpecProcessCapabilities() + // we don't care about order...and this is way faster... + removeFunc := func(s []string, i int) []string { + s[i] = s[len(s)-1] + return s[:len(s)-1] + } + for i, cap := range g.spec.Process.Capabilities.Bounding { if strings.ToUpper(cap) == cp { - g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding[:i], g.spec.Process.Capabilities.Bounding[i+1:]...) + g.spec.Process.Capabilities.Bounding = removeFunc(g.spec.Process.Capabilities.Bounding, i) } } for i, cap := range g.spec.Process.Capabilities.Effective { if strings.ToUpper(cap) == cp { - g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective[:i], g.spec.Process.Capabilities.Effective[i+1:]...) + g.spec.Process.Capabilities.Effective = removeFunc(g.spec.Process.Capabilities.Effective, i) } } for i, cap := range g.spec.Process.Capabilities.Inheritable { if strings.ToUpper(cap) == cp { - g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable[:i], g.spec.Process.Capabilities.Inheritable[i+1:]...) + g.spec.Process.Capabilities.Inheritable = removeFunc(g.spec.Process.Capabilities.Inheritable, i) } } for i, cap := range g.spec.Process.Capabilities.Permitted { if strings.ToUpper(cap) == cp { - g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted[:i], g.spec.Process.Capabilities.Permitted[i+1:]...) + g.spec.Process.Capabilities.Permitted = removeFunc(g.spec.Process.Capabilities.Permitted, i) } } for i, cap := range g.spec.Process.Capabilities.Ambient { if strings.ToUpper(cap) == cp { - g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient[:i], g.spec.Process.Capabilities.Ambient[i+1:]...) + g.spec.Process.Capabilities.Ambient = removeFunc(g.spec.Process.Capabilities.Ambient, i) } } diff --git a/vendor/github.com/opencontainers/runtime-tools/specerror/error.go b/vendor/github.com/opencontainers/runtime-tools/specerror/error.go new file mode 100644 index 00000000..c75bb6b1 --- /dev/null +++ b/vendor/github.com/opencontainers/runtime-tools/specerror/error.go @@ -0,0 +1,170 @@ +// Package specerror implements runtime-spec-specific tooling for +// tracking RFC 2119 violations. +package specerror + +import ( + "fmt" + + "github.com/hashicorp/go-multierror" + rfc2119 "github.com/opencontainers/runtime-tools/error" +) + +const referenceTemplate = "https://github.com/opencontainers/runtime-spec/blob/v%s/%s" + +// Code represents the spec violation, enumerating both +// configuration violations and runtime violations. +type Code int + +const ( + // NonError represents that an input is not an error + NonError Code = iota + // NonRFCError represents that an error is not a rfc2119 error + NonRFCError + + // ConfigFileExistence represents the error code of 'config.json' existence test + ConfigFileExistence + // ArtifactsInSingleDir represents the error code of artifacts place test + ArtifactsInSingleDir + + // SpecVersion represents the error code of specfication version test + SpecVersion + + // RootOnNonHyperV represents the error code of root setting test on non hyper-v containers + RootOnNonHyperV + // RootOnHyperV represents the error code of root setting test on hyper-v containers + RootOnHyperV + // PathFormatOnWindows represents the error code of the path format test on Window + PathFormatOnWindows + // PathName represents the error code of the path name test + PathName + // PathExistence represents the error code of the path existence test + PathExistence + // ReadonlyFilesystem represents the error code of readonly test + ReadonlyFilesystem + // ReadonlyOnWindows represents the error code of readonly setting test on Windows + ReadonlyOnWindows + + // DefaultFilesystems represents the error code of default filesystems test + DefaultFilesystems + + // CreateWithID represents the error code of 'create' lifecyle test with 'id' provided + CreateWithID + // CreateWithUniqueID represents the error code of 'create' lifecyle test with unique 'id' provided + CreateWithUniqueID + // CreateNewContainer represents the error code 'create' lifecyle test that creates new container + CreateNewContainer +) + +type errorTemplate struct { + Level rfc2119.Level + Reference func(version string) (reference string, err error) +} + +// Error represents a runtime-spec violation. +type Error struct { + // Err holds the RFC 2119 violation. + Err rfc2119.Error + + // Code is a matchable holds a Code + Code Code +} + +var ( + containerFormatRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "bundle.md#container-format"), nil + } + specVersionRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#specification-version"), nil + } + rootRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#root"), nil + } + defaultFSRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config-linux.md#default-filesystems"), nil + } + runtimeCreateRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "runtime.md#create"), nil + } +) + +var ociErrors = map[Code]errorTemplate{ + // Bundle.md + // Container Format + ConfigFileExistence: {Level: rfc2119.Must, Reference: containerFormatRef}, + ArtifactsInSingleDir: {Level: rfc2119.Must, Reference: containerFormatRef}, + + // Config.md + // Specification Version + SpecVersion: {Level: rfc2119.Must, Reference: specVersionRef}, + // Root + RootOnNonHyperV: {Level: rfc2119.Required, Reference: rootRef}, + RootOnHyperV: {Level: rfc2119.Must, Reference: rootRef}, + // TODO: add tests for 'PathFormatOnWindows' + PathFormatOnWindows: {Level: rfc2119.Must, Reference: rootRef}, + PathName: {Level: rfc2119.Should, Reference: rootRef}, + PathExistence: {Level: rfc2119.Must, Reference: rootRef}, + ReadonlyFilesystem: {Level: rfc2119.Must, Reference: rootRef}, + ReadonlyOnWindows: {Level: rfc2119.Must, Reference: rootRef}, + + // Config-Linux.md + // Default Filesystems + DefaultFilesystems: {Level: rfc2119.Should, Reference: defaultFSRef}, + + // Runtime.md + // Create + CreateWithID: {Level: rfc2119.Must, Reference: runtimeCreateRef}, + CreateWithUniqueID: {Level: rfc2119.Must, Reference: runtimeCreateRef}, + CreateNewContainer: {Level: rfc2119.Must, Reference: runtimeCreateRef}, +} + +// Error returns the error message with specification reference. +func (err *Error) Error() string { + return err.Err.Error() +} + +// NewError creates an Error referencing a spec violation. The error +// can be cast to an *Error for extracting structured information +// about the level of the violation and a reference to the violated +// spec condition. +// +// A version string (for the version of the spec that was violated) +// must be set to get a working URL. +func NewError(code Code, err error, version string) error { + template := ociErrors[code] + reference, err2 := template.Reference(version) + if err2 != nil { + return err2 + } + return &Error{ + Err: rfc2119.Error{ + Level: template.Level, + Reference: reference, + Err: err, + }, + Code: code, + } +} + +// FindError finds an error from a source error (multiple error) and +// returns the error code if found. +// If the source error is nil or empty, return NonError. +// If the source error is not a multiple error, return NonRFCError. +func FindError(err error, code Code) Code { + if err == nil { + return NonError + } + + if merr, ok := err.(*multierror.Error); ok { + if merr.ErrorOrNil() == nil { + return NonError + } + for _, e := range merr.Errors { + if rfcErr, ok := e.(*Error); ok { + if rfcErr.Code == code { + return code + } + } + } + } + return NonRFCError +} diff --git a/vendor/github.com/opencontainers/runtime-tools/validate/error.go b/vendor/github.com/opencontainers/runtime-tools/validate/error.go deleted file mode 100644 index 754d032b..00000000 --- a/vendor/github.com/opencontainers/runtime-tools/validate/error.go +++ /dev/null @@ -1,110 +0,0 @@ -package validate - -import ( - "errors" - "fmt" - "strings" - - rspec "github.com/opencontainers/runtime-spec/specs-go" -) - -// ComplianceLevel represents the OCI compliance levels -type ComplianceLevel int - -const ( - // MAY-level - - // ComplianceMay represents 'MAY' in RFC2119 - ComplianceMay ComplianceLevel = iota - // ComplianceOptional represents 'OPTIONAL' in RFC2119 - ComplianceOptional - - // SHOULD-level - - // ComplianceShould represents 'SHOULD' in RFC2119 - ComplianceShould - // ComplianceShouldNot represents 'SHOULD NOT' in RFC2119 - ComplianceShouldNot - // ComplianceRecommended represents 'RECOMMENDED' in RFC2119 - ComplianceRecommended - // ComplianceNotRecommended represents 'NOT RECOMMENDED' in RFC2119 - ComplianceNotRecommended - - // MUST-level - - // ComplianceMust represents 'MUST' in RFC2119 - ComplianceMust - // ComplianceMustNot represents 'MUST NOT' in RFC2119 - ComplianceMustNot - // ComplianceShall represents 'SHALL' in RFC2119 - ComplianceShall - // ComplianceShallNot represents 'SHALL NOT' in RFC2119 - ComplianceShallNot - // ComplianceRequired represents 'REQUIRED' in RFC2119 - ComplianceRequired -) - -// ErrorCode represents the compliance content -type ErrorCode int - -const ( - // DefaultFilesystems represents the error code of default filesystems test - DefaultFilesystems ErrorCode = iota -) - -// Error represents an error with compliance level and OCI reference -type Error struct { - Level ComplianceLevel - Reference string - Err error -} - -const referencePrefix = "https://github.com/opencontainers/runtime-spec/blob" - -var ociErrors = map[ErrorCode]Error{ - DefaultFilesystems: Error{Level: ComplianceShould, Reference: "config-linux.md#default-filesystems"}, -} - -// ParseLevel takes a string level and returns the OCI compliance level constant -func ParseLevel(level string) (ComplianceLevel, error) { - switch strings.ToUpper(level) { - case "MAY": - fallthrough - case "OPTIONAL": - return ComplianceMay, nil - case "SHOULD": - fallthrough - case "SHOULDNOT": - fallthrough - case "RECOMMENDED": - fallthrough - case "NOTRECOMMENDED": - return ComplianceShould, nil - case "MUST": - fallthrough - case "MUSTNOT": - fallthrough - case "SHALL": - fallthrough - case "SHALLNOT": - fallthrough - case "REQUIRED": - return ComplianceMust, nil - } - - var l ComplianceLevel - return l, fmt.Errorf("%q is not a valid compliance level", level) -} - -// NewError creates an Error by ErrorCode and message -func NewError(code ErrorCode, msg string) error { - err := ociErrors[code] - err.Err = errors.New(msg) - - return &err -} - -// Error returns the error message with OCI reference -func (oci *Error) Error() string { - return fmt.Sprintf("%s\nRefer to: %s/v%s/%s", oci.Err.Error(), referencePrefix, rspec.Version, oci.Reference) -} diff --git a/vendor/github.com/opencontainers/runtime-tools/validate/validate.go b/vendor/github.com/opencontainers/runtime-tools/validate/validate.go index d97865d4..bbdb29c6 100644 --- a/vendor/github.com/opencontainers/runtime-tools/validate/validate.go +++ b/vendor/github.com/opencontainers/runtime-tools/validate/validate.go @@ -3,21 +3,27 @@ package validate import ( "bufio" "encoding/json" + "errors" "fmt" "io/ioutil" "net" "os" "path/filepath" "reflect" + "regexp" "runtime" "strings" + "syscall" "unicode" "unicode/utf8" "github.com/blang/semver" + "github.com/hashicorp/go-multierror" rspec "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" "github.com/syndtr/gocapability/capability" + + "github.com/opencontainers/runtime-tools/specerror" ) const specConfig = "config.json" @@ -70,7 +76,7 @@ func NewValidatorFromPath(bundlePath string, hostSpecific bool, platform string) platform = runtime.GOOS } if bundlePath == "" { - return Validator{}, fmt.Errorf("Bundle path shouldn't be empty") + return Validator{}, fmt.Errorf("bundle path shouldn't be empty") } if _, err := os.Stat(bundlePath); err != nil { @@ -80,7 +86,7 @@ func NewValidatorFromPath(bundlePath string, hostSpecific bool, platform string) configPath := filepath.Join(bundlePath, specConfig) content, err := ioutil.ReadFile(configPath) if err != nil { - return Validator{}, err + return Validator{}, specerror.NewError(specerror.ConfigFileExistence, err, rspec.Version) } if !utf8.Valid(content) { return Validator{}, fmt.Errorf("%q is not encoded in UTF-8", configPath) @@ -94,37 +100,45 @@ func NewValidatorFromPath(bundlePath string, hostSpecific bool, platform string) } // CheckAll checks all parts of runtime bundle -func (v *Validator) CheckAll() (msgs []string) { - msgs = append(msgs, v.CheckPlatform()...) - msgs = append(msgs, v.CheckRoot()...) - msgs = append(msgs, v.CheckMandatoryFields()...) - msgs = append(msgs, v.CheckSemVer()...) - msgs = append(msgs, v.CheckMounts()...) - msgs = append(msgs, v.CheckProcess()...) - msgs = append(msgs, v.CheckHooks()...) - msgs = append(msgs, v.CheckLinux()...) +func (v *Validator) CheckAll() (errs error) { + errs = multierror.Append(errs, v.CheckPlatform()) + errs = multierror.Append(errs, v.CheckRoot()) + errs = multierror.Append(errs, v.CheckMandatoryFields()) + errs = multierror.Append(errs, v.CheckSemVer()) + errs = multierror.Append(errs, v.CheckMounts()) + errs = multierror.Append(errs, v.CheckProcess()) + errs = multierror.Append(errs, v.CheckHooks()) + errs = multierror.Append(errs, v.CheckLinux()) return } // CheckRoot checks status of v.spec.Root -func (v *Validator) CheckRoot() (msgs []string) { +func (v *Validator) CheckRoot() (errs error) { logrus.Debugf("check root") - if v.platform == "windows" && v.spec.Windows.HyperV != nil { + if v.platform == "windows" && v.spec.Windows != nil && v.spec.Windows.HyperV != nil { if v.spec.Root != nil { - msgs = append(msgs, fmt.Sprintf("for Hyper-V containers, Root must not be set")) + errs = multierror.Append(errs, + specerror.NewError(specerror.RootOnHyperV, fmt.Errorf("for Hyper-V containers, Root must not be set"), rspec.Version)) return } return } else if v.spec.Root == nil { - msgs = append(msgs, fmt.Sprintf("for non-Hyper-V containers, Root must be set")) + errs = multierror.Append(errs, + specerror.NewError(specerror.RootOnNonHyperV, fmt.Errorf("for non-Hyper-V containers, Root must be set"), rspec.Version)) return } absBundlePath, err := filepath.Abs(v.bundlePath) if err != nil { - msgs = append(msgs, fmt.Sprintf("unable to convert %q to an absolute path", v.bundlePath)) + errs = multierror.Append(errs, fmt.Errorf("unable to convert %q to an absolute path", v.bundlePath)) + return + } + + if filepath.Base(v.spec.Root.Path) != "rootfs" { + errs = multierror.Append(errs, + specerror.NewError(specerror.PathName, fmt.Errorf("path name should be the conventional 'rootfs'"), rspec.Version)) } var rootfsPath string @@ -137,24 +151,29 @@ func (v *Validator) CheckRoot() (msgs []string) { rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path) absRootPath, err = filepath.Abs(rootfsPath) if err != nil { - msgs = append(msgs, fmt.Sprintf("unable to convert %q to an absolute path", rootfsPath)) + errs = multierror.Append(errs, fmt.Errorf("unable to convert %q to an absolute path", rootfsPath)) + return } } if fi, err := os.Stat(rootfsPath); err != nil { - msgs = append(msgs, fmt.Sprintf("Cannot find the root path %q", rootfsPath)) + errs = multierror.Append(errs, + specerror.NewError(specerror.PathExistence, fmt.Errorf("cannot find the root path %q", rootfsPath), rspec.Version)) } else if !fi.IsDir() { - msgs = append(msgs, fmt.Sprintf("The root path %q is not a directory.", rootfsPath)) + errs = multierror.Append(errs, + specerror.NewError(specerror.PathExistence, fmt.Errorf("root.path %q is not a directory", rootfsPath), rspec.Version)) } rootParent := filepath.Dir(absRootPath) if absRootPath == string(filepath.Separator) || rootParent != absBundlePath { - msgs = append(msgs, fmt.Sprintf("root.path is %q, but it MUST be a child of %q", v.spec.Root.Path, absBundlePath)) + errs = multierror.Append(errs, + specerror.NewError(specerror.ArtifactsInSingleDir, fmt.Errorf("root.path is %q, but it MUST be a child of %q", v.spec.Root.Path, absBundlePath), rspec.Version)) } if v.platform == "windows" { if v.spec.Root.Readonly { - msgs = append(msgs, "root.readonly field MUST be omitted or false when target platform is windows") + errs = multierror.Append(errs, + specerror.NewError(specerror.ReadonlyOnWindows, fmt.Errorf("root.readonly field MUST be omitted or false when target platform is windows"), rspec.Version)) } } @@ -162,53 +181,54 @@ func (v *Validator) CheckRoot() (msgs []string) { } // CheckSemVer checks v.spec.Version -func (v *Validator) CheckSemVer() (msgs []string) { +func (v *Validator) CheckSemVer() (errs error) { logrus.Debugf("check semver") version := v.spec.Version _, err := semver.Parse(version) if err != nil { - msgs = append(msgs, fmt.Sprintf("%q is not valid SemVer: %s", version, err.Error())) + errs = multierror.Append(errs, + specerror.NewError(specerror.SpecVersion, fmt.Errorf("%q is not valid SemVer: %s", version, err.Error()), rspec.Version)) } if version != rspec.Version { - msgs = append(msgs, fmt.Sprintf("internal error: validate currently only handles version %s, but the supplied configuration targets %s", rspec.Version, version)) + errs = multierror.Append(errs, fmt.Errorf("validate currently only handles version %s, but the supplied configuration targets %s", rspec.Version, version)) } return } // CheckHooks check v.spec.Hooks -func (v *Validator) CheckHooks() (msgs []string) { +func (v *Validator) CheckHooks() (errs error) { logrus.Debugf("check hooks") if v.spec.Hooks != nil { - msgs = append(msgs, checkEventHooks("pre-start", v.spec.Hooks.Prestart, v.HostSpecific)...) - msgs = append(msgs, checkEventHooks("post-start", v.spec.Hooks.Poststart, v.HostSpecific)...) - msgs = append(msgs, checkEventHooks("post-stop", v.spec.Hooks.Poststop, v.HostSpecific)...) + errs = multierror.Append(errs, checkEventHooks("pre-start", v.spec.Hooks.Prestart, v.HostSpecific)) + errs = multierror.Append(errs, checkEventHooks("post-start", v.spec.Hooks.Poststart, v.HostSpecific)) + errs = multierror.Append(errs, checkEventHooks("post-stop", v.spec.Hooks.Poststop, v.HostSpecific)) } return } -func checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (msgs []string) { +func checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (errs error) { for _, hook := range hooks { if !filepath.IsAbs(hook.Path) { - msgs = append(msgs, fmt.Sprintf("The %s hook %v: is not absolute path", hookType, hook.Path)) + errs = multierror.Append(errs, fmt.Errorf("the %s hook %v: is not absolute path", hookType, hook.Path)) } if hostSpecific { fi, err := os.Stat(hook.Path) if err != nil { - msgs = append(msgs, fmt.Sprintf("Cannot find %s hook: %v", hookType, hook.Path)) + errs = multierror.Append(errs, fmt.Errorf("cannot find %s hook: %v", hookType, hook.Path)) } if fi.Mode()&0111 == 0 { - msgs = append(msgs, fmt.Sprintf("The %s hook %v: is not executable", hookType, hook.Path)) + errs = multierror.Append(errs, fmt.Errorf("the %s hook %v: is not executable", hookType, hook.Path)) } } for _, env := range hook.Env { if !envValid(env) { - msgs = append(msgs, fmt.Sprintf("Env %q for hook %v is in the invalid form.", env, hook.Path)) + errs = multierror.Append(errs, fmt.Errorf("env %q for hook %v is in the invalid form", env, hook.Path)) } } } @@ -217,7 +237,7 @@ func checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (ms } // CheckProcess checks v.spec.Process -func (v *Validator) CheckProcess() (msgs []string) { +func (v *Validator) CheckProcess() (errs error) { logrus.Debugf("check process") if v.spec.Process == nil { @@ -226,17 +246,17 @@ func (v *Validator) CheckProcess() (msgs []string) { process := v.spec.Process if !filepath.IsAbs(process.Cwd) { - msgs = append(msgs, fmt.Sprintf("cwd %q is not an absolute path", process.Cwd)) + errs = multierror.Append(errs, fmt.Errorf("cwd %q is not an absolute path", process.Cwd)) } for _, env := range process.Env { if !envValid(env) { - msgs = append(msgs, fmt.Sprintf("env %q should be in the form of 'key=value'. The left hand side must consist solely of letters, digits, and underscores '_'.", env)) + errs = multierror.Append(errs, fmt.Errorf("env %q should be in the form of 'key=value'. The left hand side must consist solely of letters, digits, and underscores '_'", env)) } } if len(process.Args) == 0 { - msgs = append(msgs, fmt.Sprintf("args must not be empty")) + errs = multierror.Append(errs, fmt.Errorf("args must not be empty")) } else { if filepath.IsAbs(process.Args[0]) { var rootfsPath string @@ -250,27 +270,27 @@ func (v *Validator) CheckProcess() (msgs []string) { if os.IsNotExist(err) { logrus.Warnf("executable %q is not available in rootfs currently", process.Args[0]) } else if err != nil { - msgs = append(msgs, err.Error()) + errs = multierror.Append(errs, err) } else { m := fileinfo.Mode() if m.IsDir() || m&0111 == 0 { - msgs = append(msgs, fmt.Sprintf("arg %q is not executable", process.Args[0])) + errs = multierror.Append(errs, fmt.Errorf("arg %q is not executable", process.Args[0])) } } } } if v.spec.Process.Capabilities != nil { - msgs = append(msgs, v.CheckCapabilities()...) + errs = multierror.Append(errs, v.CheckCapabilities()) } - msgs = append(msgs, v.CheckRlimits()...) + errs = multierror.Append(errs, v.CheckRlimits()) if v.platform == "linux" { if len(process.ApparmorProfile) > 0 { profilePath := filepath.Join(v.bundlePath, v.spec.Root.Path, "/etc/apparmor.d", process.ApparmorProfile) _, err := os.Stat(profilePath) if err != nil { - msgs = append(msgs, err.Error()) + errs = multierror.Append(errs, err) } } } @@ -279,7 +299,7 @@ func (v *Validator) CheckProcess() (msgs []string) { } // CheckCapabilities checks v.spec.Process.Capabilities -func (v *Validator) CheckCapabilities() (msgs []string) { +func (v *Validator) CheckCapabilities() (errs error) { process := v.spec.Process if v.platform == "linux" { var effective, permitted, inheritable, ambient bool @@ -303,29 +323,33 @@ func (v *Validator) CheckCapabilities() (msgs []string) { for capability, owns := range caps { if err := CapValid(capability, v.HostSpecific); err != nil { - msgs = append(msgs, fmt.Sprintf("capability %q is not valid, man capabilities(7)", capability)) + errs = multierror.Append(errs, fmt.Errorf("capability %q is not valid, man capabilities(7)", capability)) } effective, permitted, ambient, inheritable = false, false, false, false for _, set := range owns { if set == "effective" { effective = true + continue } if set == "inheritable" { inheritable = true + continue } if set == "permitted" { permitted = true + continue } if set == "ambient" { ambient = true + continue } } if effective && !permitted { - msgs = append(msgs, fmt.Sprintf("effective capability %q is not allowed, as it's not permitted", capability)) + errs = multierror.Append(errs, fmt.Errorf("effective capability %q is not allowed, as it's not permitted", capability)) } if ambient && !(effective && inheritable) { - msgs = append(msgs, fmt.Sprintf("ambient capability %q is not allowed, as it's not permitted and inheribate", capability)) + errs = multierror.Append(errs, fmt.Errorf("ambient capability %q is not allowed, as it's not permitted and inheribate", capability)) } } } else { @@ -336,15 +360,15 @@ func (v *Validator) CheckCapabilities() (msgs []string) { } // CheckRlimits checks v.spec.Process.Rlimits -func (v *Validator) CheckRlimits() (msgs []string) { +func (v *Validator) CheckRlimits() (errs error) { process := v.spec.Process for index, rlimit := range process.Rlimits { for i := index + 1; i < len(process.Rlimits); i++ { if process.Rlimits[index].Type == process.Rlimits[i].Type { - msgs = append(msgs, fmt.Sprintf("rlimit can not contain the same type %q.", process.Rlimits[index].Type)) + errs = multierror.Append(errs, fmt.Errorf("rlimit can not contain the same type %q", process.Rlimits[index].Type)) } } - msgs = append(msgs, v.rlimitValid(rlimit)...) + errs = multierror.Append(errs, v.rlimitValid(rlimit)) } return @@ -392,24 +416,49 @@ func supportedMountTypes(OS string, hostSpecific bool) (map[string]bool, error) } // CheckMounts checks v.spec.Mounts -func (v *Validator) CheckMounts() (msgs []string) { +func (v *Validator) CheckMounts() (errs error) { logrus.Debugf("check mounts") supportedTypes, err := supportedMountTypes(v.platform, v.HostSpecific) if err != nil { - msgs = append(msgs, err.Error()) + errs = multierror.Append(errs, err) return } - for _, mount := range v.spec.Mounts { - if supportedTypes != nil { - if !supportedTypes[mount.Type] { - msgs = append(msgs, fmt.Sprintf("Unsupported mount type %q", mount.Type)) + for i, mountA := range v.spec.Mounts { + if supportedTypes != nil && !supportedTypes[mountA.Type] { + errs = multierror.Append(errs, fmt.Errorf("unsupported mount type %q", mountA.Type)) + } + if v.platform == "windows" { + if err := pathValid(v.platform, mountA.Destination); err != nil { + errs = multierror.Append(errs, err) + } + if err := pathValid(v.platform, mountA.Source); err != nil { + errs = multierror.Append(errs, err) + } + } else { + if err := pathValid(v.platform, mountA.Destination); err != nil { + errs = multierror.Append(errs, err) } } - - if !filepath.IsAbs(mount.Destination) { - msgs = append(msgs, fmt.Sprintf("destination %v is not an absolute path", mount.Destination)) + for j, mountB := range v.spec.Mounts { + if i == j { + continue + } + // whether B.Desination is nested within A.Destination + nested, err := nestedValid(v.platform, mountA.Destination, mountB.Destination) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + if nested { + if v.platform == "windows" && i < j { + errs = multierror.Append(errs, fmt.Errorf("on Windows, %v nested within %v is forbidden", mountB.Destination, mountA.Destination)) + } + if i > j { + logrus.Warnf("%v will be covered by %v", mountB.Destination, mountA.Destination) + } + } } } @@ -417,17 +466,17 @@ func (v *Validator) CheckMounts() (msgs []string) { } // CheckPlatform checks v.platform -func (v *Validator) CheckPlatform() (msgs []string) { +func (v *Validator) CheckPlatform() (errs error) { logrus.Debugf("check platform") if v.platform != "linux" && v.platform != "solaris" && v.platform != "windows" { - msgs = append(msgs, fmt.Sprintf("platform %q is not supported", v.platform)) + errs = multierror.Append(errs, fmt.Errorf("platform %q is not supported", v.platform)) return } if v.platform == "windows" { if v.spec.Windows == nil { - msgs = append(msgs, "'windows' MUST be set when platform is `windows`") + errs = multierror.Append(errs, errors.New("'windows' MUST be set when platform is `windows`")) } } @@ -435,14 +484,14 @@ func (v *Validator) CheckPlatform() (msgs []string) { } // CheckLinux checks v.spec.Linux -func (v *Validator) CheckLinux() (msgs []string) { +func (v *Validator) CheckLinux() (errs error) { logrus.Debugf("check linux") if v.spec.Linux == nil { return } - var typeList = map[rspec.LinuxNamespaceType]struct { + var nsTypeList = map[rspec.LinuxNamespaceType]struct { num int newExist bool }{ @@ -458,58 +507,142 @@ func (v *Validator) CheckLinux() (msgs []string) { for index := 0; index < len(v.spec.Linux.Namespaces); index++ { ns := v.spec.Linux.Namespaces[index] if !namespaceValid(ns) { - msgs = append(msgs, fmt.Sprintf("namespace %v is invalid.", ns)) + errs = multierror.Append(errs, fmt.Errorf("namespace %v is invalid", ns)) } - tmpItem := typeList[ns.Type] + tmpItem := nsTypeList[ns.Type] tmpItem.num = tmpItem.num + 1 if tmpItem.num > 1 { - msgs = append(msgs, fmt.Sprintf("duplicated namespace %q", ns.Type)) + errs = multierror.Append(errs, fmt.Errorf("duplicated namespace %q", ns.Type)) } if len(ns.Path) == 0 { tmpItem.newExist = true } - typeList[ns.Type] = tmpItem + nsTypeList[ns.Type] = tmpItem } - if (len(v.spec.Linux.UIDMappings) > 0 || len(v.spec.Linux.GIDMappings) > 0) && !typeList[rspec.UserNamespace].newExist { - msgs = append(msgs, "UID/GID mappings requires a new User namespace to be specified as well") + if (len(v.spec.Linux.UIDMappings) > 0 || len(v.spec.Linux.GIDMappings) > 0) && !nsTypeList[rspec.UserNamespace].newExist { + errs = multierror.Append(errs, errors.New("the UID/GID mappings requires a new User namespace to be specified as well")) } else if len(v.spec.Linux.UIDMappings) > 5 { - msgs = append(msgs, "Only 5 UID mappings are allowed (linux kernel restriction).") + errs = multierror.Append(errs, errors.New("only 5 UID mappings are allowed (linux kernel restriction)")) } else if len(v.spec.Linux.GIDMappings) > 5 { - msgs = append(msgs, "Only 5 GID mappings are allowed (linux kernel restriction).") + errs = multierror.Append(errs, errors.New("only 5 GID mappings are allowed (linux kernel restriction)")) } for k := range v.spec.Linux.Sysctl { - if strings.HasPrefix(k, "net.") && !typeList[rspec.NetworkNamespace].newExist { - msgs = append(msgs, fmt.Sprintf("Sysctl %v requires a new Network namespace to be specified as well", k)) + if strings.HasPrefix(k, "net.") && !nsTypeList[rspec.NetworkNamespace].newExist { + errs = multierror.Append(errs, fmt.Errorf("sysctl %v requires a new Network namespace to be specified as well", k)) } if strings.HasPrefix(k, "fs.mqueue.") { - if !typeList[rspec.MountNamespace].newExist || !typeList[rspec.IPCNamespace].newExist { - msgs = append(msgs, fmt.Sprintf("Sysctl %v requires a new IPC namespace and Mount namespace to be specified as well", k)) + if !nsTypeList[rspec.MountNamespace].newExist || !nsTypeList[rspec.IPCNamespace].newExist { + errs = multierror.Append(errs, fmt.Errorf("sysctl %v requires a new IPC namespace and Mount namespace to be specified as well", k)) } } } - if v.platform == "linux" && !typeList[rspec.UTSNamespace].newExist && v.spec.Hostname != "" { - msgs = append(msgs, fmt.Sprintf("On Linux, hostname requires a new UTS namespace to be specified as well")) + if v.platform == "linux" && !nsTypeList[rspec.UTSNamespace].newExist && v.spec.Hostname != "" { + errs = multierror.Append(errs, fmt.Errorf("on Linux, hostname requires a new UTS namespace to be specified as well")) } + // Linux devices validation + devList := make(map[string]bool) + devTypeList := make(map[string]bool) for index := 0; index < len(v.spec.Linux.Devices); index++ { - if !deviceValid(v.spec.Linux.Devices[index]) { - msgs = append(msgs, fmt.Sprintf("device %v is invalid.", v.spec.Linux.Devices[index])) + device := v.spec.Linux.Devices[index] + if !deviceValid(device) { + errs = multierror.Append(errs, fmt.Errorf("device %v is invalid", device)) + } + + if _, exists := devList[device.Path]; exists { + errs = multierror.Append(errs, fmt.Errorf("device %s is duplicated", device.Path)) + } else { + var rootfsPath string + if filepath.IsAbs(v.spec.Root.Path) { + rootfsPath = v.spec.Root.Path + } else { + rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path) + } + absPath := filepath.Join(rootfsPath, device.Path) + fi, err := os.Stat(absPath) + if os.IsNotExist(err) { + devList[device.Path] = true + } else if err != nil { + errs = multierror.Append(errs, err) + } else { + fStat, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + errs = multierror.Append(errs, fmt.Errorf("cannot determine state for device %s", device.Path)) + continue + } + var devType string + switch fStat.Mode & syscall.S_IFMT { + case syscall.S_IFCHR: + devType = "c" + case syscall.S_IFBLK: + devType = "b" + case syscall.S_IFIFO: + devType = "p" + default: + devType = "unmatched" + } + if devType != device.Type || (devType == "c" && device.Type == "u") { + errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path)) + continue + } + if devType != "p" { + dev := fStat.Rdev + major := (dev >> 8) & 0xfff + minor := (dev & 0xff) | ((dev >> 12) & 0xfff00) + if int64(major) != device.Major || int64(minor) != device.Minor { + errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path)) + continue + } + } + if device.FileMode != nil { + expectedPerm := *device.FileMode & os.ModePerm + actualPerm := fi.Mode() & os.ModePerm + if expectedPerm != actualPerm { + errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path)) + continue + } + } + if device.UID != nil { + if *device.UID != fStat.Uid { + errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path)) + continue + } + } + if device.GID != nil { + if *device.GID != fStat.Gid { + errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path)) + continue + } + } + } + } + + // unify u->c when comparing, they are synonyms + var devID string + if device.Type == "u" { + devID = fmt.Sprintf("%s:%d:%d", "c", device.Major, device.Minor) + } else { + devID = fmt.Sprintf("%s:%d:%d", device.Type, device.Major, device.Minor) + } + + if _, exists := devTypeList[devID]; exists { + logrus.Warnf("type:%s, major:%d and minor:%d for linux devices is duplicated", device.Type, device.Major, device.Minor) + } else { + devTypeList[devID] = true } } if v.spec.Linux.Resources != nil { - ms := v.CheckLinuxResources() - msgs = append(msgs, ms...) + errs = multierror.Append(errs, v.CheckLinuxResources()) } if v.spec.Linux.Seccomp != nil { - ms := v.CheckSeccomp() - msgs = append(msgs, ms...) + errs = multierror.Append(errs, v.CheckSeccomp()) } switch v.spec.Linux.RootfsPropagation { @@ -523,18 +656,18 @@ func (v *Validator) CheckLinux() (msgs []string) { case "unbindable": case "runbindable": default: - msgs = append(msgs, "rootfsPropagation must be empty or one of \"private|rprivate|slave|rslave|shared|rshared|unbindable|runbindable\"") + errs = multierror.Append(errs, errors.New("rootfsPropagation must be empty or one of \"private|rprivate|slave|rslave|shared|rshared|unbindable|runbindable\"")) } for _, maskedPath := range v.spec.Linux.MaskedPaths { if !strings.HasPrefix(maskedPath, "/") { - msgs = append(msgs, fmt.Sprintf("maskedPath %v is not an absolute path", maskedPath)) + errs = multierror.Append(errs, fmt.Errorf("maskedPath %v is not an absolute path", maskedPath)) } } for _, readonlyPath := range v.spec.Linux.ReadonlyPaths { if !strings.HasPrefix(readonlyPath, "/") { - msgs = append(msgs, fmt.Sprintf("readonlyPath %v is not an absolute path", readonlyPath)) + errs = multierror.Append(errs, fmt.Errorf("readonlyPath %v is not an absolute path", readonlyPath)) } } @@ -542,23 +675,23 @@ func (v *Validator) CheckLinux() (msgs []string) { } // CheckLinuxResources checks v.spec.Linux.Resources -func (v *Validator) CheckLinuxResources() (msgs []string) { +func (v *Validator) CheckLinuxResources() (errs error) { logrus.Debugf("check linux resources") r := v.spec.Linux.Resources if r.Memory != nil { if r.Memory.Limit != nil && r.Memory.Swap != nil && uint64(*r.Memory.Limit) > uint64(*r.Memory.Swap) { - msgs = append(msgs, fmt.Sprintf("Minimum memoryswap should be larger than memory limit")) + errs = multierror.Append(errs, fmt.Errorf("minimum memoryswap should be larger than memory limit")) } if r.Memory.Limit != nil && r.Memory.Reservation != nil && uint64(*r.Memory.Reservation) > uint64(*r.Memory.Limit) { - msgs = append(msgs, fmt.Sprintf("Minimum memory limit should be larger than memory reservation")) + errs = multierror.Append(errs, fmt.Errorf("minimum memory limit should be larger than memory reservation")) } } if r.Network != nil && v.HostSpecific { var exist bool interfaces, err := net.Interfaces() if err != nil { - msgs = append(msgs, err.Error()) + errs = multierror.Append(errs, err) return } for _, prio := range r.Network.Priorities { @@ -570,7 +703,24 @@ func (v *Validator) CheckLinuxResources() (msgs []string) { } } if !exist { - msgs = append(msgs, fmt.Sprintf("Interface %s does not exist currently", prio.Name)) + errs = multierror.Append(errs, fmt.Errorf("interface %s does not exist currently", prio.Name)) + } + } + } + for index := 0; index < len(r.Devices); index++ { + switch r.Devices[index].Type { + case "a", "b", "c": + default: + errs = multierror.Append(errs, fmt.Errorf("type of devices %s is invalid", r.Devices[index].Type)) + } + + access := []byte(r.Devices[index].Access) + for i := 0; i < len(access); i++ { + switch access[i] { + case 'r', 'w', 'm': + default: + errs = multierror.Append(errs, fmt.Errorf("access %s is invalid", r.Devices[index].Access)) + return } } } @@ -579,16 +729,16 @@ func (v *Validator) CheckLinuxResources() (msgs []string) { } // CheckSeccomp checkc v.spec.Linux.Seccomp -func (v *Validator) CheckSeccomp() (msgs []string) { +func (v *Validator) CheckSeccomp() (errs error) { logrus.Debugf("check linux seccomp") s := v.spec.Linux.Seccomp if !seccompActionValid(s.DefaultAction) { - msgs = append(msgs, fmt.Sprintf("seccomp defaultAction %q is invalid.", s.DefaultAction)) + errs = multierror.Append(errs, fmt.Errorf("seccomp defaultAction %q is invalid", s.DefaultAction)) } for index := 0; index < len(s.Syscalls); index++ { if !syscallValid(s.Syscalls[index]) { - msgs = append(msgs, fmt.Sprintf("syscall %v is invalid.", s.Syscalls[index])) + errs = multierror.Append(errs, fmt.Errorf("syscall %v is invalid", s.Syscalls[index])) } } for index := 0; index < len(s.Architectures); index++ { @@ -612,7 +762,7 @@ func (v *Validator) CheckSeccomp() (msgs []string) { case rspec.ArchPARISC: case rspec.ArchPARISC64: default: - msgs = append(msgs, fmt.Sprintf("seccomp architecture %q is invalid", s.Architectures[index])) + errs = multierror.Append(errs, fmt.Errorf("seccomp architecture %q is invalid", s.Architectures[index])) } } @@ -629,7 +779,7 @@ func CapValid(c string, hostSpecific bool) error { for _, cap := range capability.List() { if c == fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String())) { if hostSpecific && cap > LastCap() { - return fmt.Errorf("CAP_%s is not supported on the current host", c) + return fmt.Errorf("%s is not supported on the current host", c) } isValid = true break @@ -637,7 +787,7 @@ func CapValid(c string, hostSpecific bool) error { } if !isValid { - return fmt.Errorf("Invalid capability: %s", c) + return fmt.Errorf("invalid capability: %s", c) } return nil } @@ -669,9 +819,9 @@ func envValid(env string) bool { return true } -func (v *Validator) rlimitValid(rlimit rspec.POSIXRlimit) (msgs []string) { +func (v *Validator) rlimitValid(rlimit rspec.POSIXRlimit) (errs error) { if rlimit.Hard < rlimit.Soft { - msgs = append(msgs, fmt.Sprintf("hard limit of rlimit %s should not be less than soft limit", rlimit.Type)) + errs = multierror.Append(errs, fmt.Errorf("hard limit of rlimit %s should not be less than soft limit", rlimit.Type)) } if v.platform == "linux" { @@ -680,7 +830,7 @@ func (v *Validator) rlimitValid(rlimit rspec.POSIXRlimit) (msgs []string) { return } } - msgs = append(msgs, fmt.Sprintf("rlimit type %q is invalid", rlimit.Type)) + errs = multierror.Append(errs, fmt.Errorf("rlimit type %q is invalid", rlimit.Type)) } else { logrus.Warnf("process.rlimits validation not yet implemented for platform %q", v.platform) } @@ -708,6 +858,65 @@ func namespaceValid(ns rspec.LinuxNamespace) bool { return true } +func pathValid(os, path string) error { + if os == "windows" { + matched, err := regexp.MatchString("^[a-zA-Z]:(\\\\[^\\\\/<>|:*?\"]+)+$", path) + if err != nil { + return err + } + if !matched { + return fmt.Errorf("invalid windows path %v", path) + } + return nil + } + if !filepath.IsAbs(path) { + return fmt.Errorf("%v is not an absolute path", path) + } + return nil +} + +// Check whether pathB is nested whithin pathA +func nestedValid(os, pathA, pathB string) (bool, error) { + if pathA == pathB { + return false, nil + } + if pathA == "/" && pathB != "" { + return true, nil + } + + var sep string + if os == "windows" { + sep = "\\" + } else { + sep = "/" + } + + splitedPathA := strings.Split(filepath.Clean(pathA), sep) + splitedPathB := strings.Split(filepath.Clean(pathB), sep) + lenA := len(splitedPathA) + lenB := len(splitedPathB) + + if lenA > lenB { + if (lenA - lenB) == 1 { + // if pathA is longer but not end with separator + if splitedPathA[lenA-1] != "" { + return false, nil + } + splitedPathA = splitedPathA[:lenA-1] + } else { + return false, nil + } + } + + for i, partA := range splitedPathA { + if partA != splitedPathB[i] { + return false, nil + } + } + + return true, nil +} + func deviceValid(d rspec.LinuxDevice) bool { switch d.Type { case "b", "c", "u": @@ -767,38 +976,38 @@ func isStructPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct } -func checkMandatoryUnit(field reflect.Value, tagField reflect.StructField, parent string) (msgs []string) { +func checkMandatoryUnit(field reflect.Value, tagField reflect.StructField, parent string) (errs error) { mandatory := !strings.Contains(tagField.Tag.Get("json"), "omitempty") switch field.Kind() { case reflect.Ptr: if mandatory && field.IsNil() { - msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name)) + errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name)) } case reflect.String: if mandatory && (field.Len() == 0) { - msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name)) + errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name)) } case reflect.Slice: if mandatory && (field.IsNil() || field.Len() == 0) { - msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name)) + errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name)) return } for index := 0; index < field.Len(); index++ { mValue := field.Index(index) if mValue.CanInterface() { - msgs = append(msgs, checkMandatory(mValue.Interface())...) + errs = multierror.Append(errs, checkMandatory(mValue.Interface())) } } case reflect.Map: if mandatory && (field.IsNil() || field.Len() == 0) { - msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name)) - return msgs + errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name)) + return } keys := field.MapKeys() for index := 0; index < len(keys); index++ { mValue := field.MapIndex(keys[index]) if mValue.CanInterface() { - msgs = append(msgs, checkMandatory(mValue.Interface())...) + errs = multierror.Append(errs, checkMandatory(mValue.Interface())) } } default: @@ -807,7 +1016,7 @@ func checkMandatoryUnit(field reflect.Value, tagField reflect.StructField, paren return } -func checkMandatory(obj interface{}) (msgs []string) { +func checkMandatory(obj interface{}) (errs error) { objT := reflect.TypeOf(obj) objV := reflect.ValueOf(obj) if isStructPtr(objT) { @@ -821,12 +1030,12 @@ func checkMandatory(obj interface{}) (msgs []string) { t := objT.Field(i).Type if isStructPtr(t) && objV.Field(i).IsNil() { if !strings.Contains(objT.Field(i).Tag.Get("json"), "omitempty") { - msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty", objT.Name(), objT.Field(i).Name)) + errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", objT.Name(), objT.Field(i).Name)) } } else if (isStruct(t) || isStructPtr(t)) && objV.Field(i).CanInterface() { - msgs = append(msgs, checkMandatory(objV.Field(i).Interface())...) + errs = multierror.Append(errs, checkMandatory(objV.Field(i).Interface())) } else { - msgs = append(msgs, checkMandatoryUnit(objV.Field(i), objT.Field(i), objT.Name())...) + errs = multierror.Append(errs, checkMandatoryUnit(objV.Field(i), objT.Field(i), objT.Name())) } } @@ -834,7 +1043,7 @@ func checkMandatory(obj interface{}) (msgs []string) { } // CheckMandatoryFields checks mandatory field of container's config file -func (v *Validator) CheckMandatoryFields() []string { +func (v *Validator) CheckMandatoryFields() error { logrus.Debugf("check mandatory fields") return checkMandatory(v.spec)