diff options
-rw-r--r-- | cmd/podman/main.go | 7 | ||||
-rw-r--r-- | cmd/podman/spec.go | 1 | ||||
-rw-r--r-- | cmd/podman/utils.go | 1 | ||||
-rw-r--r-- | libpod/container_internal.go | 60 | ||||
-rw-r--r-- | libpod/options.go | 14 | ||||
-rw-r--r-- | libpod/runtime.go | 4 | ||||
-rw-r--r-- | libpod/testdata/config.toml | 28 | ||||
-rw-r--r-- | pkg/hooks/hooks.go | 141 | ||||
-rw-r--r-- | test/e2e/hooks/checkhook.json (renamed from test/hooks/checkhook.json) | 2 | ||||
-rwxr-xr-x | test/e2e/hooks/checkhook.sh (renamed from test/hooks/checkhook.sh) | 0 | ||||
-rw-r--r-- | test/e2e/libpod_suite_test.go | 3 | ||||
-rw-r--r-- | test/e2e/run_test.go | 14 | ||||
-rw-r--r-- | vendor.conf | 1 | ||||
-rw-r--r-- | vendor/github.com/mrunalp/fileutils/LICENSE | 191 | ||||
-rw-r--r-- | vendor/github.com/mrunalp/fileutils/README.md | 5 | ||||
-rw-r--r-- | vendor/github.com/mrunalp/fileutils/fileutils.go | 158 | ||||
-rw-r--r-- | vendor/github.com/mrunalp/fileutils/idtools.go | 49 |
17 files changed, 677 insertions, 2 deletions
diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 2a0ca30ee..ef11f7905 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -7,6 +7,7 @@ import ( "github.com/containers/storage/pkg/reexec" "github.com/pkg/errors" + "github.com/projectatomic/libpod/pkg/hooks" "github.com/projectatomic/libpod/version" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -123,6 +124,12 @@ func main() { Usage: "path for the cpu profiling results", }, cli.StringFlag{ + Name: "hooks-dir-path", + Usage: "set the OCI hooks directory path", + Value: hooks.DefaultHooksDir, + Hidden: true, + }, + cli.StringFlag{ Name: "log-level", Usage: "log messages above specified level: debug, info, warn, error (default), fatal or panic", Value: "error", diff --git a/cmd/podman/spec.go b/cmd/podman/spec.go index 014919e17..5e98d5b50 100644 --- a/cmd/podman/spec.go +++ b/cmd/podman/spec.go @@ -378,7 +378,6 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { } /* - Hooks: &configSpec.Hooks{}, //Annotations Resources: &configSpec.LinuxResources{ BlockIO: &blkio, diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go index 4c42c8ff5..8b59e1344 100644 --- a/cmd/podman/utils.go +++ b/cmd/podman/utils.go @@ -48,6 +48,7 @@ func getRuntime(c *cli.Context) (*libpod.Runtime, error) { if c.GlobalIsSet("cni-config-dir") { options = append(options, libpod.WithCNIConfigDir(c.GlobalString("cni-config-dir"))) } + options = append(options, libpod.WithHooksDir(c.GlobalString("hooks-dir-path"))) // TODO flag to set CNI plugins dir? diff --git a/libpod/container_internal.go b/libpod/container_internal.go index f75df8c28..f3247b1c0 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" "strings" "syscall" "time" @@ -22,6 +23,7 @@ import ( "github.com/pkg/errors" crioAnnotations "github.com/projectatomic/libpod/pkg/annotations" "github.com/projectatomic/libpod/pkg/chrootuser" + "github.com/projectatomic/libpod/pkg/hooks" "github.com/projectatomic/libpod/pkg/secrets" "github.com/projectatomic/libpod/pkg/util" "github.com/sirupsen/logrus" @@ -931,6 +933,9 @@ func (c *Container) generateSpec() (*spec.Spec, error) { } } + if err := c.setupOCIHooks(&g); err != nil { + return nil, errors.Wrapf(err, "error setting up OCI Hooks") + } // Bind builtin image volumes if c.config.ImageVolumes { if err := c.addImageVolumes(&g); err != nil { @@ -1103,3 +1108,58 @@ func (c *Container) saveSpec(spec *spec.Spec) error { return nil } + +func (c *Container) setupOCIHooks(g *generate.Generator) error { + addedHooks := map[string]struct{}{} + ocihooks, err := hooks.SetupHooks(c.runtime.config.HooksDir) + if err != nil { + return err + } + addHook := func(hook hooks.HookParams) error { + // Only add a hook once + if _, ok := addedHooks[hook.Hook]; !ok { + if err := hooks.AddOCIHook(g, hook); err != nil { + return err + } + addedHooks[hook.Hook] = struct{}{} + } + return nil + } + for _, hook := range ocihooks { + logrus.Debugf("SetupOCIHooks", hook) + if hook.HasBindMounts && len(c.config.Spec.Mounts) > 0 { + if err := addHook(hook); err != nil { + return err + } + continue + } + for _, cmd := range hook.Cmds { + match, err := regexp.MatchString(cmd, c.config.Spec.Process.Args[0]) + if err != nil { + logrus.Errorf("Invalid regex %q:%q", cmd, err) + continue + } + if match { + if err := addHook(hook); err != nil { + return err + } + } + } + annotations := c.Spec().Annotations + for _, annotationRegex := range hook.Annotations { + for _, annotation := range annotations { + match, err := regexp.MatchString(annotationRegex, annotation) + if err != nil { + logrus.Errorf("Invalid regex %q:%q", annotationRegex, err) + continue + } + if match { + if err := addHook(hook); err != nil { + return err + } + } + } + } + } + return nil +} diff --git a/libpod/options.go b/libpod/options.go index 8fb6c8d2e..f9d6cb211 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -172,6 +172,20 @@ func WithStaticDir(dir string) RuntimeOption { } } +// WithHooksDir sets the directory to look for OCI runtime hooks config +// Note we are not saving this in database, since this is really just for used +// for testing +func WithHooksDir(hooksDir string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return ErrRuntimeFinalized + } + + rt.config.HooksDir = hooksDir + return nil + } +} + // WithTmpDir sets the directory that temporary runtime files which are not // expected to survive across reboots will be stored // This should be located on a tmpfs mount (/tmp or /var/run for example) diff --git a/libpod/runtime.go b/libpod/runtime.go index 869727f38..94d412c84 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -15,6 +15,7 @@ import ( "github.com/docker/docker/pkg/namesgenerator" "github.com/pkg/errors" "github.com/projectatomic/libpod/libpod/image" + "github.com/projectatomic/libpod/pkg/hooks" "github.com/sirupsen/logrus" "github.com/ulule/deepcopier" ) @@ -127,6 +128,8 @@ type RuntimeConfig struct { // CNIPluginDir sets a number of directories where the CNI network // plugins can be located CNIPluginDir []string `toml:"cni_plugin_dir"` + // HooksDir Path to the directory containing hooks configuration files + HooksDir string `toml:"hooks_dir"` } var ( @@ -153,6 +156,7 @@ var ( "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", }, CgroupManager: "cgroupfs", + HooksDir: hooks.DefaultHooksDir, StaticDir: filepath.Join(storage.DefaultStoreOptions.GraphRoot, "libpod"), TmpDir: "/var/run/libpod", MaxLogSize: -1, diff --git a/libpod/testdata/config.toml b/libpod/testdata/config.toml new file mode 100644 index 000000000..e19d36017 --- /dev/null +++ b/libpod/testdata/config.toml @@ -0,0 +1,28 @@ +[crio] + root = "/var/lib/containers/storage" + runroot = "/var/run/containers/storage" + storage_driver = "overlay2" + log_dir = "/var/log/crio/pods" + file_locking = true + [crio.runtime] + runtime = "/usr/bin/runc" + runtime_untrusted_workload = "" + default_workload_trust = "trusted" + conmon = "/usr/local/libexec/crio/conmon" + conmon_env = ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"] + selinux = true + seccomp_profile = "/etc/crio/seccomp.json" + apparmor_profile = "crio-default" + cgroup_manager = "cgroupfs" + hooks_dir_path = "/usr/share/containers/oci/hooks.d" + pids_limit = 2048 + container_exits_dir = "/var/run/podman/exits" + [crio.image] + default_transport = "docker://" + pause_image = "kubernetes/pause" + pause_command = "/pause" + signature_policy = "" + image_volumes = "mkdir" + [crio.network] + network_dir = "/etc/cni/net.d/" + plugin_dir = "/opt/cni/bin/" diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go new file mode 100644 index 000000000..dbcd7b773 --- /dev/null +++ b/pkg/hooks/hooks.go @@ -0,0 +1,141 @@ +package hooks + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" + "syscall" + + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const ( + // DefaultHooksDir Default directory containing hooks config files + DefaultHooksDir = "/usr/share/containers/oci/hooks.d" + // OverrideHooksDir Directory where admin can override the default configuration + OverrideHooksDir = "/etc/containers/oci/hooks.d" +) + +// HookParams is the structure returned from read the hooks configuration +type HookParams struct { + Hook string `json:"hook"` + Stage []string `json:"stage"` + Cmds []string `json:"cmd"` + Annotations []string `json:"annotation"` + HasBindMounts bool `json:"hasbindmounts"` + Arguments []string `json:"arguments"` +} + +// readHook reads hooks json files, verifies it and returns the json config +func readHook(hookPath string) (HookParams, error) { + var hook HookParams + raw, err := ioutil.ReadFile(hookPath) + if err != nil { + return hook, errors.Wrapf(err, "error Reading hook %q", hookPath) + } + if err := json.Unmarshal(raw, &hook); err != nil { + return hook, errors.Wrapf(err, "error Unmarshalling JSON for %q", hookPath) + } + if _, err := os.Stat(hook.Hook); err != nil { + return hook, errors.Wrapf(err, "unable to stat hook %q in hook config %q", hook.Hook, hookPath) + } + validStage := map[string]bool{"prestart": true, "poststart": true, "poststop": true} + for _, cmd := range hook.Cmds { + if _, err = regexp.Compile(cmd); err != nil { + return hook, errors.Wrapf(err, "invalid cmd regular expression %q defined in hook config %q", cmd, hookPath) + } + } + for _, cmd := range hook.Annotations { + if _, err = regexp.Compile(cmd); err != nil { + return hook, errors.Wrapf(err, "invalid cmd regular expression %q defined in hook config %q", cmd, hookPath) + } + } + for _, stage := range hook.Stage { + if !validStage[stage] { + return hook, errors.Wrapf(err, "unknown stage %q defined in hook config %q", stage, hookPath) + } + } + return hook, nil +} + +// readHooks reads hooks json files in directory to setup OCI Hooks +// adding hooks to the passedin hooks map. +func readHooks(hooksPath string, hooks map[string]HookParams) error { + if _, err := os.Stat(hooksPath); err != nil { + if os.IsNotExist(err) { + logrus.Warnf("hooks path: %q does not exist", hooksPath) + return nil + } + return errors.Wrapf(err, "unable to stat hooks path %q", hooksPath) + } + + files, err := ioutil.ReadDir(hooksPath) + if err != nil { + return err + } + + for _, file := range files { + if !strings.HasSuffix(file.Name(), ".json") { + continue + } + hook, err := readHook(filepath.Join(hooksPath, file.Name())) + if err != nil { + return err + } + for key, h := range hooks { + // hook.Hook can only be defined in one hook file, unless it has the + // same name in the override path. + if hook.Hook == h.Hook && key != file.Name() { + return errors.Wrapf(syscall.EINVAL, "duplicate path, hook %q from %q already defined in %q", hook.Hook, hooksPath, key) + } + } + hooks[file.Name()] = hook + } + return nil +} + +// SetupHooks takes a hookspath and reads all of the hooks in that directory. +// returning a map of the configured hooks +func SetupHooks(hooksPath string) (map[string]HookParams, error) { + hooksMap := make(map[string]HookParams) + if err := readHooks(hooksPath, hooksMap); err != nil { + return nil, err + } + if hooksPath == DefaultHooksDir { + if err := readHooks(OverrideHooksDir, hooksMap); err != nil { + return nil, err + } + } + + return hooksMap, nil +} + +// AddOCIHook generates OCI specification using the included hook +func AddOCIHook(g *generate.Generator, hook HookParams) error { + for _, stage := range hook.Stage { + h := spec.Hook{ + Path: hook.Hook, + Args: append([]string{hook.Hook}, hook.Arguments...), + Env: []string{fmt.Sprintf("stage=%s", stage)}, + } + logrus.Debugf("AddOCIHook", h) + switch stage { + case "prestart": + g.AddPreStartHook(h) + + case "poststart": + g.AddPostStartHook(h) + + case "poststop": + g.AddPostStopHook(h) + } + } + return nil +} diff --git a/test/hooks/checkhook.json b/test/e2e/hooks/checkhook.json index 50ff23727..5a9bc86d1 100644 --- a/test/hooks/checkhook.json +++ b/test/e2e/hooks/checkhook.json @@ -1,5 +1,5 @@ { "cmd" : [".*"], - "hook" : "HOOKSDIR/checkhook.sh", + "hook" : "/tmp/checkhook.sh", "stage" : [ "prestart" ] } diff --git a/test/hooks/checkhook.sh b/test/e2e/hooks/checkhook.sh index 8b755cb40..8b755cb40 100755 --- a/test/hooks/checkhook.sh +++ b/test/e2e/hooks/checkhook.sh diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index 5bdc112fb..a902949ba 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -156,6 +156,9 @@ func (p *PodmanTest) MakeOptions() []string { // Podman is the exec call to podman on the filesystem func (p *PodmanTest) Podman(args []string) *PodmanSession { podmanOptions := p.MakeOptions() + if os.Getenv("HOOK_OPTION") != "" { + podmanOptions = append(podmanOptions, os.Getenv("HOOK_OPTION")) + } podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...) podmanOptions = append(podmanOptions, args...) fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 16fae5898..21c577d9a 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" + "github.com/mrunalp/fileutils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -217,6 +218,19 @@ var _ = Describe("Podman run", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman test hooks", func() { + hcheck := "/run/hookscheck" + hooksDir := "/tmp/hooks" + os.Mkdir(hooksDir, 0755) + fileutils.CopyFile("hooks/hooks.json", hooksDir) + os.Setenv("HOOK_OPTION", fmt.Sprintf("--hooks-dir-path=%s", hooksDir)) + os.Remove(hcheck) + session := podmanTest.Podman([]string{"run", ALPINE, "ls"}) + session.Wait(10) + os.Unsetenv("HOOK_OPTION") + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman run with secrets", func() { containersDir := "/usr/share/containers" err := os.MkdirAll(containersDir, 0755) diff --git a/vendor.conf b/vendor.conf index 5ea0c9dac..3ce86618a 100644 --- a/vendor.conf +++ b/vendor.conf @@ -86,3 +86,4 @@ k8s.io/apiserver 4d1163080139f1f9094baf8a3a6099e85e1867f6 https://github.com/kub k8s.io/client-go 7cd1d3291b7d9b1e2d54d4b69eb65995eaf8888e https://github.com/kubernetes/client-go k8s.io/kube-openapi 275e2ce91dec4c05a4094a7b1daee5560b555ac9 https://github.com/kubernetes/kube-openapi k8s.io/utils 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e https://github.com/kubernetes/utils +github.com/mrunalp/fileutils master diff --git a/vendor/github.com/mrunalp/fileutils/LICENSE b/vendor/github.com/mrunalp/fileutils/LICENSE new file mode 100644 index 000000000..27448585a --- /dev/null +++ b/vendor/github.com/mrunalp/fileutils/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2014 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/mrunalp/fileutils/README.md b/vendor/github.com/mrunalp/fileutils/README.md new file mode 100644 index 000000000..6cb4140ea --- /dev/null +++ b/vendor/github.com/mrunalp/fileutils/README.md @@ -0,0 +1,5 @@ +# fileutils + +Collection of utilities for file manipulation in golang + +The library is based on docker pkg/archive pkg/idtools but does copies instead of handling archive formats. diff --git a/vendor/github.com/mrunalp/fileutils/fileutils.go b/vendor/github.com/mrunalp/fileutils/fileutils.go new file mode 100644 index 000000000..5a9818a24 --- /dev/null +++ b/vendor/github.com/mrunalp/fileutils/fileutils.go @@ -0,0 +1,158 @@ +package fileutils + +import ( + "fmt" + "io" + "os" + "path/filepath" + "syscall" +) + +// CopyFile copies the file at source to dest +func CopyFile(source string, dest string) error { + si, err := os.Lstat(source) + if err != nil { + return err + } + + st, ok := si.Sys().(*syscall.Stat_t) + if !ok { + return fmt.Errorf("could not convert to syscall.Stat_t") + } + + uid := int(st.Uid) + gid := int(st.Gid) + + // Handle symlinks + if si.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(source) + if err != nil { + return err + } + if err := os.Symlink(target, dest); err != nil { + return err + } + } + + // Handle device files + if st.Mode&syscall.S_IFMT == syscall.S_IFBLK || st.Mode&syscall.S_IFMT == syscall.S_IFCHR { + devMajor := int64(major(uint64(st.Rdev))) + devMinor := int64(minor(uint64(st.Rdev))) + mode := uint32(si.Mode() & 07777) + if st.Mode&syscall.S_IFMT == syscall.S_IFBLK { + mode |= syscall.S_IFBLK + } + if st.Mode&syscall.S_IFMT == syscall.S_IFCHR { + mode |= syscall.S_IFCHR + } + if err := syscall.Mknod(dest, mode, int(mkdev(devMajor, devMinor))); err != nil { + return err + } + } + + // Handle regular files + if si.Mode().IsRegular() { + sf, err := os.Open(source) + if err != nil { + return err + } + defer sf.Close() + + df, err := os.Create(dest) + if err != nil { + return err + } + defer df.Close() + + _, err = io.Copy(df, sf) + if err != nil { + return err + } + } + + // Chown the file + if err := os.Lchown(dest, uid, gid); err != nil { + return err + } + + // Chmod the file + if !(si.Mode()&os.ModeSymlink == os.ModeSymlink) { + if err := os.Chmod(dest, si.Mode()); err != nil { + return err + } + } + + return nil +} + +// CopyDirectory copies the files under the source directory +// to dest directory. The dest directory is created if it +// does not exist. +func CopyDirectory(source string, dest string) error { + fi, err := os.Stat(source) + if err != nil { + return err + } + + // Get owner. + st, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return fmt.Errorf("could not convert to syscall.Stat_t") + } + + // We have to pick an owner here anyway. + if err := MkdirAllNewAs(dest, fi.Mode(), int(st.Uid), int(st.Gid)); err != nil { + return err + } + + return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Get the relative path + relPath, err := filepath.Rel(source, path) + if err != nil { + return nil + } + + if info.IsDir() { + // Skip the source directory. + if path != source { + // Get the owner. + st, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return fmt.Errorf("could not convert to syscall.Stat_t") + } + + uid := int(st.Uid) + gid := int(st.Gid) + + if err := os.Mkdir(filepath.Join(dest, relPath), info.Mode()); err != nil { + return err + } + + if err := os.Lchown(filepath.Join(dest, relPath), uid, gid); err != nil { + return err + } + } + return nil + } + + return CopyFile(path, filepath.Join(dest, relPath)) + }) +} + +// Gives a number indicating the device driver to be used to access the passed device +func major(device uint64) uint64 { + return (device >> 8) & 0xfff +} + +// Gives a number that serves as a flag to the device driver for the passed device +func minor(device uint64) uint64 { + return (device & 0xff) | ((device >> 12) & 0xfff00) +} + +func mkdev(major int64, minor int64) uint32 { + return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff)) +} diff --git a/vendor/github.com/mrunalp/fileutils/idtools.go b/vendor/github.com/mrunalp/fileutils/idtools.go new file mode 100644 index 000000000..161aec8f5 --- /dev/null +++ b/vendor/github.com/mrunalp/fileutils/idtools.go @@ -0,0 +1,49 @@ +package fileutils + +import ( + "os" + "path/filepath" +) + +// MkdirAllNewAs creates a directory (include any along the path) and then modifies +// ownership ONLY of newly created directories to the requested uid/gid. If the +// directories along the path exist, no change of ownership will be performed +func MkdirAllNewAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { + // make an array containing the original path asked for, plus (for mkAll == true) + // all path components leading up to the complete path that don't exist before we MkdirAll + // so that we can chown all of them properly at the end. If chownExisting is false, we won't + // chown the full directory path if it exists + var paths []string + if _, err := os.Stat(path); err != nil && os.IsNotExist(err) { + paths = []string{path} + } else if err == nil { + // nothing to do; directory path fully exists already + return nil + } + + // walk back to "/" looking for directories which do not exist + // and add them to the paths array for chown after creation + dirPath := path + for { + dirPath = filepath.Dir(dirPath) + if dirPath == "/" { + break + } + if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) { + paths = append(paths, dirPath) + } + } + + if err := os.MkdirAll(path, mode); err != nil && !os.IsExist(err) { + return err + } + + // even if it existed, we will chown the requested path + any subpaths that + // didn't exist when we called MkdirAll + for _, pathComponent := range paths { + if err := os.Chown(pathComponent, ownerUID, ownerGID); err != nil { + return err + } + } + return nil +} |