| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,25 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "os" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/openshift/origin/pkg/cmd/recycle" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func main() {
|
|
| 10 |
+ basename := os.Args[0] |
|
| 11 |
+ args := os.Args[1:] |
|
| 12 |
+ if len(args) == 0 || len(args[0]) == 0 {
|
|
| 13 |
+ fmt.Printf("Usage: %s DIR\n", basename)
|
|
| 14 |
+ os.Exit(1) |
|
| 15 |
+ } |
|
| 16 |
+ |
|
| 17 |
+ if err := recycle.Recycle(args[0]); err != nil {
|
|
| 18 |
+ fmt.Printf("Scrub failed: %v\n", err)
|
|
| 19 |
+ os.Exit(1) |
|
| 20 |
+ } |
|
| 21 |
+ |
|
| 22 |
+ fmt.Println("Scrub OK")
|
|
| 23 |
+ os.Exit(0) |
|
| 24 |
+} |
| ... | ... |
@@ -49,6 +49,7 @@ cp -pf "${imagedir}/hello-openshift" examples/hello-openshift/bin
|
| 49 | 49 |
cp -pf "${imagedir}/deployment" examples/deployment/bin
|
| 50 | 50 |
cp -pf "${imagedir}/gitserver" examples/gitserver/bin
|
| 51 | 51 |
cp -pf "${imagedir}/dockerregistry" images/dockerregistry/bin
|
| 52 |
+cp -pf "${imagedir}/recycle" images/recycler/bin
|
|
| 52 | 53 |
|
| 53 | 54 |
# Copy SDN scripts into images/node |
| 54 | 55 |
os::util::install-sdn "${OS_ROOT}" "${OS_ROOT}/images/node"
|
| ... | ... |
@@ -31,6 +31,7 @@ readonly OS_IMAGE_COMPILE_TARGETS=( |
| 31 | 31 |
images/pod |
| 32 | 32 |
cmd/dockerregistry |
| 33 | 33 |
cmd/gitserver |
| 34 |
+ cmd/recycle |
|
| 34 | 35 |
) |
| 35 | 36 |
readonly OS_SCRATCH_IMAGE_COMPILE_TARGETS=( |
| 36 | 37 |
examples/hello-openshift |
| ... | ... |
@@ -59,7 +60,6 @@ readonly OS_ALL_BINARIES=("${OS_ALL_TARGETS[@]##*/}")
|
| 59 | 59 |
readonly OPENSHIFT_BINARY_SYMLINKS=( |
| 60 | 60 |
openshift-router |
| 61 | 61 |
openshift-deploy |
| 62 |
- openshift-recycle |
|
| 63 | 62 |
openshift-sti-build |
| 64 | 63 |
openshift-docker-build |
| 65 | 64 |
origin |
| ... | ... |
@@ -1,17 +1,10 @@ |
| 1 | 1 |
# |
| 2 |
-# This is the default deployment strategy image for OpenShift Origin. It expects a set of |
|
| 3 |
-# environment variables to parameterize the deploy: |
|
| 4 |
-# |
|
| 5 |
-# KUBERNETES_MASTER - the address of the OpenShift master |
|
| 6 |
-# KUBERNETES_DEPLOYMENT_ID - the deployment identifier that is running this build |
|
| 2 |
+# This is the default OpenShift Origin persistent volume recycler image. |
|
| 7 | 3 |
# |
| 8 | 4 |
# The standard name for this image is openshift/origin-recycler |
| 9 | 5 |
# |
| 10 |
-FROM openshift/origin |
|
| 11 |
- |
|
| 12 |
-RUN yum install -y sudo && \ |
|
| 13 |
- sed -i -e "s/Defaults requiretty.*/ #Defaults requiretty/g" /etc/sudoers |
|
| 6 |
+FROM scratch |
|
| 14 | 7 |
|
| 15 |
-ADD scripts/recycler.sh /usr/share/openshift/scripts/volumes/recycler.sh |
|
| 8 |
+ADD bin/recycle /usr/bin/recycle |
|
| 16 | 9 |
|
| 17 |
-ENTRYPOINT ["/usr/share/openshift/scripts/volumes/recycler.sh"] |
|
| 10 |
+ENTRYPOINT ["/usr/bin/recycle"] |
| 0 | 1 |
deleted file mode 100755 |
| ... | ... |
@@ -1,49 +0,0 @@ |
| 1 |
-#!/bin/bash |
|
| 2 |
- |
|
| 3 |
-# 'recycler' performs an 'rm -rf' on a volume to scrub it clean before it's |
|
| 4 |
-# reused as a cluster resource. This script is intended to be used in a pod that |
|
| 5 |
-# performs the scrub. The container in the pod should succeed or fail based on |
|
| 6 |
-# the exit status of this script. |
|
| 7 |
- |
|
| 8 |
-set -o errexit |
|
| 9 |
-set -o noglob |
|
| 10 |
-set -o nounset |
|
| 11 |
-set -o pipefail |
|
| 12 |
- |
|
| 13 |
-if [[ $# -ne 1 ]]; then |
|
| 14 |
- echo >&2 "Usage: $0 some/path/to/scrub" |
|
| 15 |
- exit 1 |
|
| 16 |
-fi |
|
| 17 |
- |
|
| 18 |
-# first and only arg is the directory to scrub |
|
| 19 |
-dir="${1}"
|
|
| 20 |
- |
|
| 21 |
-if [[ ! -d "${dir}" ]]; then
|
|
| 22 |
- echo >&2 "Error: scrub directory '${dir}' does not exist"
|
|
| 23 |
- exit 1 |
|
| 24 |
-fi |
|
| 25 |
- |
|
| 26 |
-# shred regular files |
|
| 27 |
-function recycle_file() {
|
|
| 28 |
- filename="${1}"
|
|
| 29 |
- uid=$(stat -c "#%u" "${filename}")
|
|
| 30 |
- sudo -u "${uid}" shred "${filename}"
|
|
| 31 |
-} |
|
| 32 |
-export -f recycle_file |
|
| 33 |
- |
|
| 34 |
-find "${dir}" -type f -print0 | xargs -r -n 1 -0 bash -c 'recycle_file "$@"' {}
|
|
| 35 |
- |
|
| 36 |
-# rm all |
|
| 37 |
-function rm_all() {
|
|
| 38 |
- filename="${1}"
|
|
| 39 |
- uid=$(stat -c "#%u" "${filename}")
|
|
| 40 |
- sudo -u "${uid}" rm -rf "${filename}"
|
|
| 41 |
-} |
|
| 42 |
-export -f rm_all |
|
| 43 |
- |
|
| 44 |
-find "${dir}" ! -type d -print0 | xargs -r -n 1 -0 bash -c 'rm_all "$@"' {}
|
|
| 45 |
- |
|
| 46 |
-find "${dir}" -mindepth 1 -type d -print0 | sort -zrg | xargs -r -n 1 -0 bash -c 'rm_all "$@"' {}
|
|
| 47 |
- |
|
| 48 |
-echo "Scrub OK" |
|
| 49 |
-exit 0 |
| 50 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,16 @@ |
| 0 |
+package recycle |
|
| 1 |
+ |
|
| 2 |
+import "os" |
|
| 3 |
+ |
|
| 4 |
+// Recycle recursively deletes files and folders within the given path. It does not delete the path itself. |
|
| 5 |
+func Recycle(dir string) error {
|
|
| 6 |
+ return newWalker(func(path string, info os.FileInfo) error {
|
|
| 7 |
+ // Leave the root dir alone |
|
| 8 |
+ if path == dir {
|
|
| 9 |
+ return nil |
|
| 10 |
+ } |
|
| 11 |
+ |
|
| 12 |
+ // Delete all subfiles/subdirs |
|
| 13 |
+ return os.Remove(path) |
|
| 14 |
+ }).Walk(dir) |
|
| 15 |
+} |
| 0 | 16 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,89 @@ |
| 0 |
+package recycle |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io/ioutil" |
|
| 4 |
+ "os" |
|
| 5 |
+ "path" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ "testing" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func TestRecycle(t *testing.T) {
|
|
| 11 |
+ root, err := ioutil.TempDir("", "recycler-test-")
|
|
| 12 |
+ if err != nil {
|
|
| 13 |
+ t.Fatal(err) |
|
| 14 |
+ } |
|
| 15 |
+ defer func() {
|
|
| 16 |
+ if err := os.RemoveAll(root); err != nil {
|
|
| 17 |
+ t.Fatal(err) |
|
| 18 |
+ } |
|
| 19 |
+ }() |
|
| 20 |
+ |
|
| 21 |
+ filenames := []string{
|
|
| 22 |
+ string([]byte{3}), // Ctrl+C
|
|
| 23 |
+ string([]byte{4}), // Ctrl+D
|
|
| 24 |
+ |
|
| 25 |
+ `white space`, |
|
| 26 |
+ `new |
|
| 27 |
+ line`, |
|
| 28 |
+ |
|
| 29 |
+ `*`, |
|
| 30 |
+ `~`, |
|
| 31 |
+ `\`, |
|
| 32 |
+ `\\`, |
|
| 33 |
+ |
|
| 34 |
+ ` && touch and-escape`, |
|
| 35 |
+ ` || touch or-escape`, |
|
| 36 |
+ ` ; touch semi-escape`, |
|
| 37 |
+ ` " touch quote-escape`, |
|
| 38 |
+ ` ' touch apos-escape`, |
|
| 39 |
+ ` }"; touch brace-escape`, |
|
| 40 |
+ |
|
| 41 |
+ `env x='() { :;}; echo vulnerable'`, // shellshock
|
|
| 42 |
+ |
|
| 43 |
+ `$USER`, |
|
| 44 |
+ |
|
| 45 |
+ `...`, |
|
| 46 |
+ `.file`, |
|
| 47 |
+ |
|
| 48 |
+ `䏿–‡`, // utf-8 |
|
| 49 |
+ `κόσμε`, // utf-8 |
|
| 50 |
+ `Iñtërnâtiônà lizætiøn`, // utf-8 |
|
| 51 |
+ } |
|
| 52 |
+ |
|
| 53 |
+ for _, dir := range filenames {
|
|
| 54 |
+ dirpath := path.Join(root, dir) |
|
| 55 |
+ if err := os.Mkdir(dirpath, os.FileMode(0755)); err != nil {
|
|
| 56 |
+ t.Errorf("Error writing dir %s\n%v", dirpath, err)
|
|
| 57 |
+ continue |
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 60 |
+ for _, file := range filenames {
|
|
| 61 |
+ filepath := path.Join(dirpath, file) |
|
| 62 |
+ if err := ioutil.WriteFile(filepath, []byte(filepath), os.FileMode(0755)); err != nil {
|
|
| 63 |
+ t.Errorf("Error writing file %s\n%v", filepath, err)
|
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 66 |
+ if _, err := os.Stat(filepath); err != nil {
|
|
| 67 |
+ t.Errorf("Error verifying file %s\n%v", filepath, err)
|
|
| 68 |
+ } |
|
| 69 |
+ } |
|
| 70 |
+ } |
|
| 71 |
+ |
|
| 72 |
+ err = Recycle(root) |
|
| 73 |
+ if err != nil {
|
|
| 74 |
+ t.Error(err) |
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ remaining := []string{}
|
|
| 78 |
+ err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
| 79 |
+ remaining = append(remaining, path) |
|
| 80 |
+ return err |
|
| 81 |
+ }) |
|
| 82 |
+ if err != nil {
|
|
| 83 |
+ t.Errorf("Unexpected error: %v", err)
|
|
| 84 |
+ } |
|
| 85 |
+ if len(remaining) != 1 || remaining[0] != root {
|
|
| 86 |
+ t.Errorf("Unexpected files left after recycling: %#v", remaining)
|
|
| 87 |
+ } |
|
| 88 |
+} |
| 0 | 89 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,179 @@ |
| 0 |
+package recycle |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "os" |
|
| 5 |
+ "path/filepath" |
|
| 6 |
+ "runtime" |
|
| 7 |
+ "sort" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+type walkFunc func(path string, info os.FileInfo) error |
|
| 11 |
+ |
|
| 12 |
+// Walker visits a directory tree, depth-first, calling walkFn for every file/directory. |
|
| 13 |
+// It calls setfsuid with the owning UID of each directory before calling walkFn for the direct children of that directory. |
|
| 14 |
+type walker struct {
|
|
| 15 |
+ walkFn walkFunc |
|
| 16 |
+ |
|
| 17 |
+ // fsuid holds our current fsuid |
|
| 18 |
+ fsuid int64 |
|
| 19 |
+ |
|
| 20 |
+ // lstat is for testing, defaults to os.Lstat |
|
| 21 |
+ lstat func(path string) (os.FileInfo, error) |
|
| 22 |
+ |
|
| 23 |
+ // getuid is for testing, defaults to fileinfo.Sys().(*syscall.Stat_t).Uid |
|
| 24 |
+ getuid func(info os.FileInfo) (int64, error) |
|
| 25 |
+ |
|
| 26 |
+ // setfsuid is for testing, defaults to syscall.Setfsuid |
|
| 27 |
+ setfsuid func(uid int) error |
|
| 28 |
+ |
|
| 29 |
+ // readDirNames is for testing, defaults to readDirNames |
|
| 30 |
+ readDirNames func(dirname string) ([]string, error) |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+type walkError struct {
|
|
| 34 |
+ path string |
|
| 35 |
+ info os.FileInfo |
|
| 36 |
+ operation string |
|
| 37 |
+ err error |
|
| 38 |
+} |
|
| 39 |
+ |
|
| 40 |
+func (w walkError) Error() string {
|
|
| 41 |
+ var mode interface{} = "unknown"
|
|
| 42 |
+ if w.info != nil {
|
|
| 43 |
+ mode = w.info.Mode() |
|
| 44 |
+ } |
|
| 45 |
+ return fmt.Sprintf("%s (%s), %s: %s", w.path, mode, w.operation, w.err)
|
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+func makeWalkError(path string, info os.FileInfo, err error, operation string) error {
|
|
| 49 |
+ if _, isWalkError := err.(walkError); isWalkError {
|
|
| 50 |
+ return err |
|
| 51 |
+ } |
|
| 52 |
+ return walkError{path, info, operation, err}
|
|
| 53 |
+} |
|
| 54 |
+ |
|
| 55 |
+func newWalker(walkFn walkFunc) *walker {
|
|
| 56 |
+ return &walker{
|
|
| 57 |
+ walkFn: walkFn, |
|
| 58 |
+ fsuid: int64(os.Getuid()), // default to the uid of the process |
|
| 59 |
+ lstat: os.Lstat, |
|
| 60 |
+ getuid: getuid, |
|
| 61 |
+ setfsuid: setfsuid, |
|
| 62 |
+ readDirNames: readDirNames, |
|
| 63 |
+ } |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+func (w *walker) Walk(root string) error {
|
|
| 67 |
+ // Lock threads, so our Setfsuid calls always apply to the same thread |
|
| 68 |
+ runtime.LockOSThread() |
|
| 69 |
+ defer runtime.UnlockOSThread() |
|
| 70 |
+ |
|
| 71 |
+ // The launching process must have the ability to stat the root dir |
|
| 72 |
+ info, err := w.lstat(root) |
|
| 73 |
+ if err != nil {
|
|
| 74 |
+ return makeWalkError(root, info, err, "lstat root dir") |
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ // become the root dir's owner to begin |
|
| 78 |
+ err = w.becomeOwner(info) |
|
| 79 |
+ if err != nil {
|
|
| 80 |
+ return makeWalkError(root, info, err, "becoming root dir owner") |
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ return w.walk(root, info) |
|
| 84 |
+} |
|
| 85 |
+ |
|
| 86 |
+func (w *walker) walk(path string, info os.FileInfo) error {
|
|
| 87 |
+ var err error |
|
| 88 |
+ |
|
| 89 |
+ // Descend first |
|
| 90 |
+ if info.IsDir() {
|
|
| 91 |
+ // Remember our current fsuid |
|
| 92 |
+ previousFSuid := w.fsuid |
|
| 93 |
+ |
|
| 94 |
+ // become the dir's owner, in order to list/rmdir/unlink child files |
|
| 95 |
+ err = w.becomeOwner(info) |
|
| 96 |
+ if err != nil {
|
|
| 97 |
+ return makeWalkError(path, info, err, "becoming dir owner") |
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ // read dir info |
|
| 101 |
+ names, err := w.readDirNames(path) |
|
| 102 |
+ if err != nil {
|
|
| 103 |
+ return makeWalkError(path, info, err, fmt.Sprintf("reading dir names as %d", w.fsuid))
|
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ // visit files |
|
| 107 |
+ for _, name := range names {
|
|
| 108 |
+ filename := filepath.Join(path, name) |
|
| 109 |
+ |
|
| 110 |
+ fileInfo, err := w.lstat(filename) |
|
| 111 |
+ if err != nil {
|
|
| 112 |
+ return makeWalkError(path, info, err, fmt.Sprintf("lstat child as %d", w.fsuid))
|
|
| 113 |
+ } |
|
| 114 |
+ |
|
| 115 |
+ err = w.walk(filename, fileInfo) |
|
| 116 |
+ if err != nil {
|
|
| 117 |
+ return err |
|
| 118 |
+ } |
|
| 119 |
+ } |
|
| 120 |
+ |
|
| 121 |
+ // Return to our previous fsuid, in order to rmdir the current directory |
|
| 122 |
+ err = w.becomeUid(previousFSuid) |
|
| 123 |
+ if err != nil {
|
|
| 124 |
+ return makeWalkError(path, info, err, "returning to previous uid") |
|
| 125 |
+ } |
|
| 126 |
+ } |
|
| 127 |
+ |
|
| 128 |
+ // visit the current file |
|
| 129 |
+ err = w.walkFn(path, info) |
|
| 130 |
+ if err != nil {
|
|
| 131 |
+ return makeWalkError(path, info, err, "calling walkFn") |
|
| 132 |
+ } |
|
| 133 |
+ |
|
| 134 |
+ return nil |
|
| 135 |
+} |
|
| 136 |
+ |
|
| 137 |
+func (w *walker) becomeOwner(info os.FileInfo) error {
|
|
| 138 |
+ // get the UID |
|
| 139 |
+ uid, err := w.getuid(info) |
|
| 140 |
+ if err != nil {
|
|
| 141 |
+ return err |
|
| 142 |
+ } |
|
| 143 |
+ |
|
| 144 |
+ return w.becomeUid(uid) |
|
| 145 |
+} |
|
| 146 |
+ |
|
| 147 |
+func (w *walker) becomeUid(uid int64) error {
|
|
| 148 |
+ // if we already were the UID, no-op |
|
| 149 |
+ if w.fsuid == uid {
|
|
| 150 |
+ return nil |
|
| 151 |
+ } |
|
| 152 |
+ |
|
| 153 |
+ // become the UID |
|
| 154 |
+ if err := w.setfsuid(int(uid)); err != nil {
|
|
| 155 |
+ return err |
|
| 156 |
+ } |
|
| 157 |
+ |
|
| 158 |
+ // remember the last UID we became |
|
| 159 |
+ w.fsuid = uid |
|
| 160 |
+ |
|
| 161 |
+ return nil |
|
| 162 |
+} |
|
| 163 |
+ |
|
| 164 |
+// readDirNames reads the directory named by dirname and returns a sorted list of directory entries. |
|
| 165 |
+func readDirNames(dirname string) ([]string, error) {
|
|
| 166 |
+ f, err := os.Open(dirname) |
|
| 167 |
+ if err != nil {
|
|
| 168 |
+ return nil, err |
|
| 169 |
+ } |
|
| 170 |
+ defer f.Close() |
|
| 171 |
+ |
|
| 172 |
+ names, err := f.Readdirnames(-1) |
|
| 173 |
+ if err != nil {
|
|
| 174 |
+ return nil, err |
|
| 175 |
+ } |
|
| 176 |
+ sort.Strings(names) |
|
| 177 |
+ return names, nil |
|
| 178 |
+} |
| 0 | 179 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,23 @@ |
| 0 |
+// +build linux |
|
| 1 |
+ |
|
| 2 |
+package recycle |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "errors" |
|
| 6 |
+ "os" |
|
| 7 |
+ "syscall" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+var StatError = errors.New("fileinfo.Sys() is not *syscall.Stat_t")
|
|
| 11 |
+ |
|
| 12 |
+func getuid(info os.FileInfo) (int64, error) {
|
|
| 13 |
+ stat_t, ok := info.Sys().(*syscall.Stat_t) |
|
| 14 |
+ if !ok {
|
|
| 15 |
+ return 0, StatError |
|
| 16 |
+ } |
|
| 17 |
+ return int64(stat_t.Uid), nil |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+func setfsuid(uid int) (err error) {
|
|
| 21 |
+ return syscall.Setfsuid(uid) |
|
| 22 |
+} |
| 0 | 23 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,15 @@ |
| 0 |
+// +build !linux |
|
| 1 |
+ |
|
| 2 |
+package recycle |
|
| 3 |
+ |
|
| 4 |
+import "os" |
|
| 5 |
+ |
|
| 6 |
+func getuid(info os.FileInfo) (int64, error) {
|
|
| 7 |
+ // no-op on non-linux platforms |
|
| 8 |
+ return 0, nil |
|
| 9 |
+} |
|
| 10 |
+ |
|
| 11 |
+func setfsuid(uid int) (err error) {
|
|
| 12 |
+ // no-op on non-linux platforms |
|
| 13 |
+ return nil |
|
| 14 |
+} |
| 0 | 15 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,154 @@ |
| 0 |
+package recycle |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io/ioutil" |
|
| 4 |
+ "os" |
|
| 5 |
+ "path/filepath" |
|
| 6 |
+ "reflect" |
|
| 7 |
+ "testing" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func TestStatUID(t *testing.T) {
|
|
| 11 |
+ root, err := ioutil.TempDir("", "walker-test-")
|
|
| 12 |
+ if err != nil {
|
|
| 13 |
+ t.Fatal(err) |
|
| 14 |
+ } |
|
| 15 |
+ defer func() {
|
|
| 16 |
+ if err := os.RemoveAll(root); err != nil {
|
|
| 17 |
+ t.Fatal(err) |
|
| 18 |
+ } |
|
| 19 |
+ }() |
|
| 20 |
+ |
|
| 21 |
+ rootName := filepath.Base(root) |
|
| 22 |
+ |
|
| 23 |
+ files := map[string]testFile{
|
|
| 24 |
+ filepath.Join(root): {uid: 0, dir: true},
|
|
| 25 |
+ filepath.Join(root, "dir1"): {uid: 1, dir: true},
|
|
| 26 |
+ filepath.Join(root, "dir2"): {uid: 2, dir: true},
|
|
| 27 |
+ filepath.Join(root, "dir2/subdir"): {uid: 2, dir: true},
|
|
| 28 |
+ filepath.Join(root, "dir2/subfile1"): {uid: 123},
|
|
| 29 |
+ filepath.Join(root, "dir2/subfile2"): {uid: 234},
|
|
| 30 |
+ filepath.Join(root, "file1"): {uid: 345},
|
|
| 31 |
+ filepath.Join(root, "file2"): {uid: 456},
|
|
| 32 |
+ } |
|
| 33 |
+ for path, fileinfo := range files {
|
|
| 34 |
+ if fileinfo.dir {
|
|
| 35 |
+ if err := os.MkdirAll(path, os.FileMode(0755)); err != nil {
|
|
| 36 |
+ t.Fatalf("Error writing dir %s\n%v", path, err)
|
|
| 37 |
+ continue |
|
| 38 |
+ } |
|
| 39 |
+ } else {
|
|
| 40 |
+ if err := os.MkdirAll(filepath.Dir(path), os.FileMode(0755)); err != nil {
|
|
| 41 |
+ t.Fatalf("Error writing dir %s\n%v", path, err)
|
|
| 42 |
+ continue |
|
| 43 |
+ } |
|
| 44 |
+ if err := ioutil.WriteFile(path, []byte(path), os.FileMode(0755)); err != nil {
|
|
| 45 |
+ t.Fatalf("Error writing file %s\n%v", path, err)
|
|
| 46 |
+ } |
|
| 47 |
+ } |
|
| 48 |
+ } |
|
| 49 |
+ |
|
| 50 |
+ expectedActions := []testAction{
|
|
| 51 |
+ // get root info, list files |
|
| 52 |
+ // no setfsuid call, because we are already uid 0 |
|
| 53 |
+ {"lstat", rootName},
|
|
| 54 |
+ {"readDirNames", rootName},
|
|
| 55 |
+ |
|
| 56 |
+ // get dir1 info, become owner, list files, change back to root owner, walk dir1 (as owner of root dir, so we can rmdir dir1) |
|
| 57 |
+ {"lstat", "dir1"},
|
|
| 58 |
+ {"setfsuid", 1},
|
|
| 59 |
+ {"readDirNames", "dir1"},
|
|
| 60 |
+ {"setfsuid", 0},
|
|
| 61 |
+ {"walk", "dir1"},
|
|
| 62 |
+ |
|
| 63 |
+ // get dir2 info, become owner, list files |
|
| 64 |
+ {"lstat", "dir2"},
|
|
| 65 |
+ {"setfsuid", 2},
|
|
| 66 |
+ {"readDirNames", "dir2"},
|
|
| 67 |
+ // get subdir info, list files, walk |
|
| 68 |
+ // no setfsuid calls to subdir owner or back to dir2 owner, because we are already uid 2 |
|
| 69 |
+ {"lstat", "subdir"},
|
|
| 70 |
+ {"readDirNames", "subdir"},
|
|
| 71 |
+ {"walk", "subdir"},
|
|
| 72 |
+ // stat and walk subfiles, no fsuid changes needed for files |
|
| 73 |
+ {"lstat", "subfile1"},
|
|
| 74 |
+ {"walk", "subfile1"},
|
|
| 75 |
+ {"lstat", "subfile2"},
|
|
| 76 |
+ {"walk", "subfile2"},
|
|
| 77 |
+ // change back to root owner, walk dir2 (as owner of root dir, so we can rmdir dir2) |
|
| 78 |
+ {"setfsuid", 0},
|
|
| 79 |
+ {"walk", "dir2"},
|
|
| 80 |
+ |
|
| 81 |
+ // stat and walk files, no fsuid changes needed for files |
|
| 82 |
+ {"lstat", "file1"},
|
|
| 83 |
+ {"walk", "file1"},
|
|
| 84 |
+ {"lstat", "file2"},
|
|
| 85 |
+ {"walk", "file2"},
|
|
| 86 |
+ |
|
| 87 |
+ // finally, walk the root |
|
| 88 |
+ {"walk", rootName},
|
|
| 89 |
+ } |
|
| 90 |
+ actions := []testAction{}
|
|
| 91 |
+ |
|
| 92 |
+ w := &walker{
|
|
| 93 |
+ fsuid: 0, // mock starting as uid 0 |
|
| 94 |
+ walkFn: func(path string, info os.FileInfo) error {
|
|
| 95 |
+ actions = append(actions, testAction{"walk", filepath.Base(path)})
|
|
| 96 |
+ return nil |
|
| 97 |
+ }, |
|
| 98 |
+ lstat: func(path string) (os.FileInfo, error) {
|
|
| 99 |
+ actions = append(actions, testAction{"lstat", filepath.Base(path)})
|
|
| 100 |
+ l, err := os.Lstat(path) |
|
| 101 |
+ return testFileInfoWrapper{l, files[path]}, err
|
|
| 102 |
+ }, |
|
| 103 |
+ getuid: func(info os.FileInfo) (int64, error) {
|
|
| 104 |
+ return info.(testFileInfoWrapper).testData.uid, nil |
|
| 105 |
+ }, |
|
| 106 |
+ setfsuid: func(uid int) error {
|
|
| 107 |
+ actions = append(actions, testAction{"setfsuid", uid})
|
|
| 108 |
+ return nil |
|
| 109 |
+ }, |
|
| 110 |
+ readDirNames: func(path string) ([]string, error) {
|
|
| 111 |
+ actions = append(actions, testAction{"readDirNames", filepath.Base(path)})
|
|
| 112 |
+ return readDirNames(path) |
|
| 113 |
+ }, |
|
| 114 |
+ } |
|
| 115 |
+ |
|
| 116 |
+ err = w.Walk(root) |
|
| 117 |
+ if err != nil {
|
|
| 118 |
+ t.Fatalf("Unexpected error: %v", err)
|
|
| 119 |
+ } |
|
| 120 |
+ |
|
| 121 |
+ for i, action := range actions {
|
|
| 122 |
+ if len(expectedActions) < i+1 {
|
|
| 123 |
+ t.Errorf("%d unexpected actions: %+v", len(actions)-len(expectedActions), actions[i:])
|
|
| 124 |
+ break |
|
| 125 |
+ } |
|
| 126 |
+ |
|
| 127 |
+ expectedAction := expectedActions[i] |
|
| 128 |
+ if !reflect.DeepEqual(expectedAction, action) {
|
|
| 129 |
+ t.Errorf("%d: expected %#v\ngot %#v", i, expectedAction, action)
|
|
| 130 |
+ continue |
|
| 131 |
+ } |
|
| 132 |
+ } |
|
| 133 |
+ |
|
| 134 |
+ if len(expectedActions) > len(actions) {
|
|
| 135 |
+ t.Errorf("%d additional expected actions:%+v", len(expectedActions)-len(actions), expectedActions[len(actions):])
|
|
| 136 |
+ } |
|
| 137 |
+ |
|
| 138 |
+} |
|
| 139 |
+ |
|
| 140 |
+type testFile struct {
|
|
| 141 |
+ uid int64 |
|
| 142 |
+ dir bool |
|
| 143 |
+} |
|
| 144 |
+ |
|
| 145 |
+type testAction struct {
|
|
| 146 |
+ Action string |
|
| 147 |
+ Data interface{}
|
|
| 148 |
+} |
|
| 149 |
+ |
|
| 150 |
+type testFileInfoWrapper struct {
|
|
| 151 |
+ os.FileInfo |
|
| 152 |
+ testData testFile |
|
| 153 |
+} |
| ... | ... |
@@ -80,9 +80,10 @@ func (c *MasterConfig) RunPersistentVolumeClaimRecycler(recyclerImageName string |
| 80 | 80 |
uid := int64(0) |
| 81 | 81 |
defaultScrubPod := volume.NewPersistentVolumeRecyclerPodTemplate() |
| 82 | 82 |
defaultScrubPod.Spec.Containers[0].Image = recyclerImageName |
| 83 |
- defaultScrubPod.Spec.Containers[0].Command = []string{"/usr/share/openshift/scripts/volumes/recycler.sh"}
|
|
| 83 |
+ defaultScrubPod.Spec.Containers[0].Command = []string{"/usr/bin/recycle"}
|
|
| 84 | 84 |
defaultScrubPod.Spec.Containers[0].Args = []string{"/scrub"}
|
| 85 | 85 |
defaultScrubPod.Spec.Containers[0].SecurityContext = &kapi.SecurityContext{RunAsUser: &uid}
|
| 86 |
+ defaultScrubPod.Spec.Containers[0].ImagePullPolicy = kapi.PullIfNotPresent |
|
| 86 | 87 |
|
| 87 | 88 |
hostPathConfig := volume.VolumeConfig{
|
| 88 | 89 |
RecyclerMinimumTimeout: 30, |