123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986 |
- // +build linux
- /*
- Copyright 2016 The Kubernetes Authors.
- 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.
- */
- package util
- import (
- "encoding/base64"
- "io/ioutil"
- "os"
- "path/filepath"
- "reflect"
- "strings"
- "testing"
- "k8s.io/apimachinery/pkg/util/sets"
- utiltesting "k8s.io/client-go/util/testing"
- )
- func TestNewAtomicWriter(t *testing.T) {
- targetDir, err := utiltesting.MkTmpdir("atomic-write")
- if err != nil {
- t.Fatalf("unexpected error creating tmp dir: %v", err)
- }
- defer os.RemoveAll(targetDir)
- _, err = NewAtomicWriter(targetDir, "-test-")
- if err != nil {
- t.Fatalf("unexpected error creating writer for existing target dir: %v", err)
- }
- nonExistentDir, err := utiltesting.MkTmpdir("atomic-write")
- if err != nil {
- t.Fatalf("unexpected error creating tmp dir: %v", err)
- }
- err = os.Remove(nonExistentDir)
- if err != nil {
- t.Fatalf("unexpected error ensuring dir %v does not exist: %v", nonExistentDir, err)
- }
- _, err = NewAtomicWriter(nonExistentDir, "-test-")
- if err == nil {
- t.Fatalf("unexpected success creating writer for nonexistent target dir: %v", err)
- }
- }
- func TestValidatePath(t *testing.T) {
- maxPath := strings.Repeat("a", maxPathLength+1)
- maxFile := strings.Repeat("a", maxFileNameLength+1)
- cases := []struct {
- name string
- path string
- valid bool
- }{
- {
- name: "valid 1",
- path: "i/am/well/behaved.txt",
- valid: true,
- },
- {
- name: "valid 2",
- path: "keepyourheaddownandfollowtherules.txt",
- valid: true,
- },
- {
- name: "max path length",
- path: maxPath,
- valid: false,
- },
- {
- name: "max file length",
- path: maxFile,
- valid: false,
- },
- {
- name: "absolute failure",
- path: "/dev/null",
- valid: false,
- },
- {
- name: "reserved path",
- path: "..sneaky.txt",
- valid: false,
- },
- {
- name: "contains doubledot 1",
- path: "hello/there/../../../../../../etc/passwd",
- valid: false,
- },
- {
- name: "contains doubledot 2",
- path: "hello/../etc/somethingbad",
- valid: false,
- },
- {
- name: "empty",
- path: "",
- valid: false,
- },
- }
- for _, tc := range cases {
- err := validatePath(tc.path)
- if tc.valid && err != nil {
- t.Errorf("%v: unexpected failure: %v", tc.name, err)
- continue
- }
- if !tc.valid && err == nil {
- t.Errorf("%v: unexpected success", tc.name)
- }
- }
- }
- func TestPathsToRemove(t *testing.T) {
- cases := []struct {
- name string
- payload1 map[string]FileProjection
- payload2 map[string]FileProjection
- expected sets.String
- }{
- {
- name: "simple",
- payload1: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- "bar.txt": {Mode: 0644, Data: []byte("bar")},
- },
- payload2: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- },
- expected: sets.NewString("bar.txt"),
- },
- {
- name: "simple 2",
- payload1: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- "zip/bar.txt": {Mode: 0644, Data: []byte("zip/b}ar")},
- },
- payload2: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- },
- expected: sets.NewString("zip/bar.txt", "zip"),
- },
- {
- name: "subdirs 1",
- payload1: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- "zip/zap/bar.txt": {Mode: 0644, Data: []byte("zip/bar")},
- },
- payload2: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- },
- expected: sets.NewString("zip/zap/bar.txt", "zip", "zip/zap"),
- },
- {
- name: "subdirs 2",
- payload1: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- "zip/1/2/3/4/bar.txt": {Mode: 0644, Data: []byte("zip/b}ar")},
- },
- payload2: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- },
- expected: sets.NewString("zip/1/2/3/4/bar.txt", "zip", "zip/1", "zip/1/2", "zip/1/2/3", "zip/1/2/3/4"),
- },
- {
- name: "subdirs 3",
- payload1: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- "zip/1/2/3/4/bar.txt": {Mode: 0644, Data: []byte("zip/b}ar")},
- "zap/a/b/c/bar.txt": {Mode: 0644, Data: []byte("zap/bar")},
- },
- payload2: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- },
- expected: sets.NewString("zip/1/2/3/4/bar.txt", "zip", "zip/1", "zip/1/2", "zip/1/2/3", "zip/1/2/3/4", "zap", "zap/a", "zap/a/b", "zap/a/b/c", "zap/a/b/c/bar.txt"),
- },
- {
- name: "subdirs 4",
- payload1: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- "zap/1/2/3/4/bar.txt": {Mode: 0644, Data: []byte("zip/bar")},
- "zap/1/2/c/bar.txt": {Mode: 0644, Data: []byte("zap/bar")},
- "zap/1/2/magic.txt": {Mode: 0644, Data: []byte("indigo")},
- },
- payload2: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- "zap/1/2/magic.txt": {Mode: 0644, Data: []byte("indigo")},
- },
- expected: sets.NewString("zap/1/2/3/4/bar.txt", "zap/1/2/3", "zap/1/2/3/4", "zap/1/2/3/4/bar.txt", "zap/1/2/c", "zap/1/2/c/bar.txt"),
- },
- {
- name: "subdirs 5",
- payload1: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- "zap/1/2/3/4/bar.txt": {Mode: 0644, Data: []byte("zip/bar")},
- "zap/1/2/c/bar.txt": {Mode: 0644, Data: []byte("zap/bar")},
- },
- payload2: map[string]FileProjection{
- "foo.txt": {Mode: 0644, Data: []byte("foo")},
- "zap/1/2/magic.txt": {Mode: 0644, Data: []byte("indigo")},
- },
- expected: sets.NewString("zap/1/2/3/4/bar.txt", "zap/1/2/3", "zap/1/2/3/4", "zap/1/2/3/4/bar.txt", "zap/1/2/c", "zap/1/2/c/bar.txt"),
- },
- }
- for _, tc := range cases {
- targetDir, err := utiltesting.MkTmpdir("atomic-write")
- if err != nil {
- t.Errorf("%v: unexpected error creating tmp dir: %v", tc.name, err)
- continue
- }
- defer os.RemoveAll(targetDir)
- writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
- err = writer.Write(tc.payload1)
- if err != nil {
- t.Errorf("%v: unexpected error writing: %v", tc.name, err)
- continue
- }
- dataDirPath := filepath.Join(targetDir, dataDirName)
- oldTsDir, err := os.Readlink(dataDirPath)
- if err != nil && os.IsNotExist(err) {
- t.Errorf("Data symlink does not exist: %v", dataDirPath)
- continue
- } else if err != nil {
- t.Errorf("Unable to read symlink %v: %v", dataDirPath, err)
- continue
- }
- actual, err := writer.pathsToRemove(tc.payload2, filepath.Join(targetDir, oldTsDir))
- if err != nil {
- t.Errorf("%v: unexpected error determining paths to remove: %v", tc.name, err)
- continue
- }
- if e, a := tc.expected, actual; !e.Equal(a) {
- t.Errorf("%v: unexpected paths to remove:\nexpected: %v\n got: %v", tc.name, e, a)
- }
- }
- }
- func TestWriteOnce(t *testing.T) {
- // $1 if you can tell me what this binary is
- encodedMysteryBinary := `f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAeABAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAOAAB
- AAAAAAAAAAEAAAAFAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAfQAAAAAAAAB9AAAAAAAAAAAA
- IAAAAAAAsDyZDwU=`
- mysteryBinaryBytes := make([]byte, base64.StdEncoding.DecodedLen(len(encodedMysteryBinary)))
- numBytes, err := base64.StdEncoding.Decode(mysteryBinaryBytes, []byte(encodedMysteryBinary))
- if err != nil {
- t.Fatalf("Unexpected error decoding binary payload: %v", err)
- }
- if numBytes != 125 {
- t.Fatalf("Unexpected decoded binary size: expected 125, got %v", numBytes)
- }
- cases := []struct {
- name string
- payload map[string]FileProjection
- success bool
- }{
- {
- name: "invalid payload 1",
- payload: map[string]FileProjection{
- "foo": {Mode: 0644, Data: []byte("foo")},
- "..bar": {Mode: 0644, Data: []byte("bar")},
- "binary.bin": {Mode: 0644, Data: mysteryBinaryBytes},
- },
- success: false,
- },
- {
- name: "invalid payload 2",
- payload: map[string]FileProjection{
- "foo/../bar": {Mode: 0644, Data: []byte("foo")},
- },
- success: false,
- },
- {
- name: "basic 1",
- payload: map[string]FileProjection{
- "foo": {Mode: 0644, Data: []byte("foo")},
- "bar": {Mode: 0644, Data: []byte("bar")},
- },
- success: true,
- },
- {
- name: "basic 2",
- payload: map[string]FileProjection{
- "binary.bin": {Mode: 0644, Data: mysteryBinaryBytes},
- ".binary.bin": {Mode: 0644, Data: mysteryBinaryBytes},
- },
- success: true,
- },
- {
- name: "basic mode 1",
- payload: map[string]FileProjection{
- "foo": {Mode: 0777, Data: []byte("foo")},
- "bar": {Mode: 0400, Data: []byte("bar")},
- },
- success: true,
- },
- {
- name: "dotfiles",
- payload: map[string]FileProjection{
- "foo": {Mode: 0644, Data: []byte("foo")},
- "bar": {Mode: 0644, Data: []byte("bar")},
- ".dotfile": {Mode: 0644, Data: []byte("dotfile")},
- ".dotfile.file": {Mode: 0644, Data: []byte("dotfile.file")},
- },
- success: true,
- },
- {
- name: "dotfiles mode",
- payload: map[string]FileProjection{
- "foo": {Mode: 0407, Data: []byte("foo")},
- "bar": {Mode: 0440, Data: []byte("bar")},
- ".dotfile": {Mode: 0777, Data: []byte("dotfile")},
- ".dotfile.file": {Mode: 0666, Data: []byte("dotfile.file")},
- },
- success: true,
- },
- {
- name: "subdirectories 1",
- payload: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt")},
- },
- success: true,
- },
- {
- name: "subdirectories mode 1",
- payload: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0400, Data: []byte("foo/bar")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt")},
- },
- success: true,
- },
- {
- name: "subdirectories 2",
- payload: map[string]FileProjection{
- "foo//bar.txt": {Mode: 0644, Data: []byte("foo//bar")},
- "bar///bar/zab.txt": {Mode: 0644, Data: []byte("bar/../bar/zab.txt")},
- },
- success: true,
- },
- {
- name: "subdirectories 3",
- payload: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt")},
- "foo/blaz/bar.txt": {Mode: 0644, Data: []byte("foo/blaz/bar")},
- "bar/zib/zab.txt": {Mode: 0644, Data: []byte("bar/zib/zab.txt")},
- },
- success: true,
- },
- {
- name: "kitchen sink",
- payload: map[string]FileProjection{
- "foo.log": {Mode: 0644, Data: []byte("foo")},
- "bar.zap": {Mode: 0644, Data: []byte("bar")},
- ".dotfile": {Mode: 0644, Data: []byte("dotfile")},
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt")},
- "foo/blaz/bar.txt": {Mode: 0644, Data: []byte("foo/blaz/bar")},
- "bar/zib/zab.txt": {Mode: 0400, Data: []byte("bar/zib/zab.txt")},
- "1/2/3/4/5/6/7/8/9/10/.dotfile.lib": {Mode: 0777, Data: []byte("1-2-3-dotfile")},
- },
- success: true,
- },
- }
- for _, tc := range cases {
- targetDir, err := utiltesting.MkTmpdir("atomic-write")
- if err != nil {
- t.Errorf("%v: unexpected error creating tmp dir: %v", tc.name, err)
- continue
- }
- defer os.RemoveAll(targetDir)
- writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
- err = writer.Write(tc.payload)
- if err != nil && tc.success {
- t.Errorf("%v: unexpected error writing payload: %v", tc.name, err)
- continue
- } else if err == nil && !tc.success {
- t.Errorf("%v: unexpected success", tc.name)
- continue
- } else if err != nil {
- continue
- }
- checkVolumeContents(targetDir, tc.name, tc.payload, t)
- }
- }
- func TestUpdate(t *testing.T) {
- cases := []struct {
- name string
- first map[string]FileProjection
- next map[string]FileProjection
- shouldWrite bool
- }{
- {
- name: "update",
- first: map[string]FileProjection{
- "foo": {Mode: 0644, Data: []byte("foo")},
- "bar": {Mode: 0644, Data: []byte("bar")},
- },
- next: map[string]FileProjection{
- "foo": {Mode: 0644, Data: []byte("foo2")},
- "bar": {Mode: 0640, Data: []byte("bar2")},
- },
- shouldWrite: true,
- },
- {
- name: "no update",
- first: map[string]FileProjection{
- "foo": {Mode: 0644, Data: []byte("foo")},
- "bar": {Mode: 0644, Data: []byte("bar")},
- },
- next: map[string]FileProjection{
- "foo": {Mode: 0644, Data: []byte("foo")},
- "bar": {Mode: 0644, Data: []byte("bar")},
- },
- shouldWrite: false,
- },
- {
- name: "no update 2",
- first: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar")},
- },
- next: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar")},
- },
- shouldWrite: false,
- },
- {
- name: "add 1",
- first: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar")},
- },
- next: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar")},
- "blu/zip.txt": {Mode: 0644, Data: []byte("zip")},
- },
- shouldWrite: true,
- },
- {
- name: "add 2",
- first: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar")},
- },
- next: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar")},
- "blu/two/2/3/4/5/zip.txt": {Mode: 0644, Data: []byte("zip")},
- },
- shouldWrite: true,
- },
- {
- name: "add 3",
- first: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar")},
- },
- next: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar")},
- "bar/2/3/4/5/zip.txt": {Mode: 0644, Data: []byte("zip")},
- },
- shouldWrite: true,
- },
- {
- name: "delete 1",
- first: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar")},
- },
- next: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- },
- shouldWrite: true,
- },
- {
- name: "delete 2",
- first: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/1/2/3/zab.txt": {Mode: 0644, Data: []byte("bar")},
- },
- next: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- },
- shouldWrite: true,
- },
- {
- name: "delete 3",
- first: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/1/2/sip.txt": {Mode: 0644, Data: []byte("sip")},
- "bar/1/2/3/zab.txt": {Mode: 0644, Data: []byte("bar")},
- },
- next: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/1/2/sip.txt": {Mode: 0644, Data: []byte("sip")},
- },
- shouldWrite: true,
- },
- {
- name: "delete 4",
- first: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/1/2/sip.txt": {Mode: 0644, Data: []byte("sip")},
- "bar/1/2/3/4/5/6zab.txt": {Mode: 0644, Data: []byte("bar")},
- },
- next: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/1/2/sip.txt": {Mode: 0644, Data: []byte("sip")},
- },
- shouldWrite: true,
- },
- {
- name: "delete all",
- first: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- "bar/1/2/sip.txt": {Mode: 0644, Data: []byte("sip")},
- "bar/1/2/3/4/5/6zab.txt": {Mode: 0644, Data: []byte("bar")},
- },
- next: map[string]FileProjection{},
- shouldWrite: true,
- },
- {
- name: "add and delete 1",
- first: map[string]FileProjection{
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
- },
- next: map[string]FileProjection{
- "bar/baz.txt": {Mode: 0644, Data: []byte("baz")},
- },
- shouldWrite: true,
- },
- }
- for _, tc := range cases {
- targetDir, err := utiltesting.MkTmpdir("atomic-write")
- if err != nil {
- t.Errorf("%v: unexpected error creating tmp dir: %v", tc.name, err)
- continue
- }
- defer os.RemoveAll(targetDir)
- writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
- err = writer.Write(tc.first)
- if err != nil {
- t.Errorf("%v: unexpected error writing: %v", tc.name, err)
- continue
- }
- checkVolumeContents(targetDir, tc.name, tc.first, t)
- if !tc.shouldWrite {
- continue
- }
- err = writer.Write(tc.next)
- if err != nil {
- if tc.shouldWrite {
- t.Errorf("%v: unexpected error writing: %v", tc.name, err)
- continue
- }
- } else if !tc.shouldWrite {
- t.Errorf("%v: unexpected success", tc.name)
- continue
- }
- checkVolumeContents(targetDir, tc.name, tc.next, t)
- }
- }
- func TestMultipleUpdates(t *testing.T) {
- cases := []struct {
- name string
- payloads []map[string]FileProjection
- }{
- {
- name: "update 1",
- payloads: []map[string]FileProjection{
- {
- "foo": {Mode: 0644, Data: []byte("foo")},
- "bar": {Mode: 0644, Data: []byte("bar")},
- },
- {
- "foo": {Mode: 0400, Data: []byte("foo2")},
- "bar": {Mode: 0400, Data: []byte("bar2")},
- },
- {
- "foo": {Mode: 0600, Data: []byte("foo3")},
- "bar": {Mode: 0600, Data: []byte("bar3")},
- },
- },
- },
- {
- name: "update 2",
- payloads: []map[string]FileProjection{
- {
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt")},
- },
- {
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar2")},
- "bar/zab.txt": {Mode: 0400, Data: []byte("bar/zab.txt2")},
- },
- },
- },
- {
- name: "clear sentinel",
- payloads: []map[string]FileProjection{
- {
- "foo": {Mode: 0644, Data: []byte("foo")},
- "bar": {Mode: 0644, Data: []byte("bar")},
- },
- {
- "foo": {Mode: 0644, Data: []byte("foo2")},
- "bar": {Mode: 0644, Data: []byte("bar2")},
- },
- {
- "foo": {Mode: 0644, Data: []byte("foo3")},
- "bar": {Mode: 0644, Data: []byte("bar3")},
- },
- {
- "foo": {Mode: 0644, Data: []byte("foo4")},
- "bar": {Mode: 0644, Data: []byte("bar4")},
- },
- },
- },
- {
- name: "subdirectories 2",
- payloads: []map[string]FileProjection{
- {
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt")},
- "foo/blaz/bar.txt": {Mode: 0644, Data: []byte("foo/blaz/bar")},
- "bar/zib/zab.txt": {Mode: 0644, Data: []byte("bar/zib/zab.txt")},
- },
- {
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar2")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt2")},
- "foo/blaz/bar.txt": {Mode: 0644, Data: []byte("foo/blaz/bar2")},
- "bar/zib/zab.txt": {Mode: 0644, Data: []byte("bar/zib/zab.txt2")},
- },
- },
- },
- {
- name: "add 1",
- payloads: []map[string]FileProjection{
- {
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar")},
- "bar//zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt")},
- "foo/blaz/bar.txt": {Mode: 0644, Data: []byte("foo/blaz/bar")},
- "bar/zib////zib/zab.txt": {Mode: 0644, Data: []byte("bar/zib/zab.txt")},
- },
- {
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar2")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt2")},
- "foo/blaz/bar.txt": {Mode: 0644, Data: []byte("foo/blaz/bar2")},
- "bar/zib/zab.txt": {Mode: 0644, Data: []byte("bar/zib/zab.txt2")},
- "add/new/keys.txt": {Mode: 0644, Data: []byte("addNewKeys")},
- },
- },
- },
- {
- name: "add 2",
- payloads: []map[string]FileProjection{
- {
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar2")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt2")},
- "foo/blaz/bar.txt": {Mode: 0644, Data: []byte("foo/blaz/bar2")},
- "bar/zib/zab.txt": {Mode: 0644, Data: []byte("bar/zib/zab.txt2")},
- "add/new/keys.txt": {Mode: 0644, Data: []byte("addNewKeys")},
- },
- {
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar2")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt2")},
- "foo/blaz/bar.txt": {Mode: 0644, Data: []byte("foo/blaz/bar2")},
- "bar/zib/zab.txt": {Mode: 0644, Data: []byte("bar/zib/zab.txt2")},
- "add/new/keys.txt": {Mode: 0644, Data: []byte("addNewKeys")},
- "add/new/keys2.txt": {Mode: 0644, Data: []byte("addNewKeys2")},
- "add/new/keys3.txt": {Mode: 0644, Data: []byte("addNewKeys3")},
- },
- },
- },
- {
- name: "remove 1",
- payloads: []map[string]FileProjection{
- {
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar")},
- "bar//zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt")},
- "foo/blaz/bar.txt": {Mode: 0644, Data: []byte("foo/blaz/bar")},
- "zip/zap/zup/fop.txt": {Mode: 0644, Data: []byte("zip/zap/zup/fop.txt")},
- },
- {
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar2")},
- "bar/zab.txt": {Mode: 0644, Data: []byte("bar/zab.txt2")},
- },
- {
- "foo/bar.txt": {Mode: 0644, Data: []byte("foo/bar")},
- },
- },
- },
- }
- for _, tc := range cases {
- targetDir, err := utiltesting.MkTmpdir("atomic-write")
- if err != nil {
- t.Errorf("%v: unexpected error creating tmp dir: %v", tc.name, err)
- continue
- }
- defer os.RemoveAll(targetDir)
- writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
- for _, payload := range tc.payloads {
- writer.Write(payload)
- checkVolumeContents(targetDir, tc.name, payload, t)
- }
- }
- }
- func checkVolumeContents(targetDir, tcName string, payload map[string]FileProjection, t *testing.T) {
- dataDirPath := filepath.Join(targetDir, dataDirName)
- // use filepath.Walk to reconstruct the payload, then deep equal
- observedPayload := make(map[string]FileProjection)
- visitor := func(path string, info os.FileInfo, err error) error {
- if info.IsDir() {
- return nil
- }
- relativePath := strings.TrimPrefix(path, dataDirPath)
- relativePath = strings.TrimPrefix(relativePath, "/")
- if strings.HasPrefix(relativePath, "..") {
- return nil
- }
- content, err := ioutil.ReadFile(path)
- if err != nil {
- return err
- }
- fileInfo, err := os.Stat(path)
- if err != nil {
- return err
- }
- mode := int32(fileInfo.Mode())
- observedPayload[relativePath] = FileProjection{Data: content, Mode: mode}
- return nil
- }
- d, err := ioutil.ReadDir(targetDir)
- if err != nil {
- t.Errorf("Unable to read dir %v: %v", targetDir, err)
- return
- }
- for _, info := range d {
- if strings.HasPrefix(info.Name(), "..") {
- continue
- }
- if info.Mode()&os.ModeSymlink != 0 {
- p := filepath.Join(targetDir, info.Name())
- actual, err := os.Readlink(p)
- if err != nil {
- t.Errorf("Unable to read symlink %v: %v", p, err)
- continue
- }
- if err := filepath.Walk(filepath.Join(targetDir, actual), visitor); err != nil {
- t.Errorf("%v: unexpected error walking directory: %v", tcName, err)
- }
- }
- }
- cleanPathPayload := make(map[string]FileProjection, len(payload))
- for k, v := range payload {
- cleanPathPayload[filepath.Clean(k)] = v
- }
- if !reflect.DeepEqual(cleanPathPayload, observedPayload) {
- t.Errorf("%v: payload and observed payload do not match.", tcName)
- }
- }
- func TestValidatePayload(t *testing.T) {
- maxPath := strings.Repeat("a", maxPathLength+1)
- cases := []struct {
- name string
- payload map[string]FileProjection
- expected sets.String
- valid bool
- }{
- {
- name: "valid payload",
- payload: map[string]FileProjection{
- "foo": {},
- "bar": {},
- },
- valid: true,
- expected: sets.NewString("foo", "bar"),
- },
- {
- name: "payload with path length > 4096 is invalid",
- payload: map[string]FileProjection{
- maxPath: {},
- },
- valid: false,
- },
- {
- name: "payload with absolute path is invalid",
- payload: map[string]FileProjection{
- "/dev/null": {},
- },
- valid: false,
- },
- {
- name: "payload with reserved path is invalid",
- payload: map[string]FileProjection{
- "..sneaky.txt": {},
- },
- valid: false,
- },
- {
- name: "payload with doubledot path is invalid",
- payload: map[string]FileProjection{
- "foo/../etc/password": {},
- },
- valid: false,
- },
- {
- name: "payload with empty path is invalid",
- payload: map[string]FileProjection{
- "": {},
- },
- valid: false,
- },
- {
- name: "payload with unclean path should be cleaned",
- payload: map[string]FileProjection{
- "foo////bar": {},
- },
- valid: true,
- expected: sets.NewString("foo/bar"),
- },
- }
- getPayloadPaths := func(payload map[string]FileProjection) sets.String {
- paths := sets.NewString()
- for path := range payload {
- paths.Insert(path)
- }
- return paths
- }
- for _, tc := range cases {
- real, err := validatePayload(tc.payload)
- if !tc.valid && err == nil {
- t.Errorf("%v: unexpected success", tc.name)
- }
- if tc.valid {
- if err != nil {
- t.Errorf("%v: unexpected failure: %v", tc.name, err)
- continue
- }
- realPaths := getPayloadPaths(real)
- if !realPaths.Equal(tc.expected) {
- t.Errorf("%v: unexpected payload paths: %v is not equal to %v", tc.name, realPaths, tc.expected)
- }
- }
- }
- }
- func TestCreateUserVisibleFiles(t *testing.T) {
- cases := []struct {
- name string
- payload map[string]FileProjection
- expected map[string]string
- }{
- {
- name: "simple path",
- payload: map[string]FileProjection{
- "foo": {},
- "bar": {},
- },
- expected: map[string]string{
- "foo": "..data/foo",
- "bar": "..data/bar",
- },
- },
- {
- name: "simple nested path",
- payload: map[string]FileProjection{
- "foo/bar": {},
- "foo/bar/txt": {},
- "bar/txt": {},
- },
- expected: map[string]string{
- "foo": "..data/foo",
- "bar": "..data/bar",
- },
- },
- {
- name: "unclean nested path",
- payload: map[string]FileProjection{
- "./bar": {},
- "foo///bar": {},
- },
- expected: map[string]string{
- "bar": "..data/bar",
- "foo": "..data/foo",
- },
- },
- }
- for _, tc := range cases {
- targetDir, err := utiltesting.MkTmpdir("atomic-write")
- if err != nil {
- t.Errorf("%v: unexpected error creating tmp dir: %v", tc.name, err)
- continue
- }
- defer os.RemoveAll(targetDir)
- dataDirPath := filepath.Join(targetDir, dataDirName)
- err = os.MkdirAll(dataDirPath, 0755)
- if err != nil {
- t.Fatalf("%v: unexpected error creating data path: %v", tc.name, err)
- }
- writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
- payload, err := validatePayload(tc.payload)
- if err != nil {
- t.Fatalf("%v: unexpected error validating payload: %v", tc.name, err)
- }
- err = writer.createUserVisibleFiles(payload)
- if err != nil {
- t.Fatalf("%v: unexpected error creating visible files: %v", tc.name, err)
- }
- for subpath, expectedDest := range tc.expected {
- visiblePath := filepath.Join(targetDir, subpath)
- destination, err := os.Readlink(visiblePath)
- if err != nil && os.IsNotExist(err) {
- t.Fatalf("%v: visible symlink does not exist: %v", tc.name, visiblePath)
- } else if err != nil {
- t.Fatalf("%v: unable to read symlink %v: %v", tc.name, dataDirPath, err)
- }
- if expectedDest != destination {
- t.Fatalf("%v: symlink destination %q not same with expected data dir %q", tc.name, destination, expectedDest)
- }
- }
- }
- }
|