Skip to content

Commit

Permalink
Allow backup only one file
Browse files Browse the repository at this point in the history
Until now we it was possible only to backup entire directories, with this fix
it is going to also single files.

Resolves #35
  • Loading branch information
rafaeljusto committed Mar 6, 2017
1 parent 5482c1e commit 67b285d
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 40 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fix content range format in multipart strategy
- Fix hash calculation (tree hash) of the uploaded archive
- Check if the audit file exists when listing it
- Remove backup when checksums don't match
- Remove backup when checksum does not match
- Allow to backup only one file

### Added
- Verifies the hash of each uploaded part in multipart strategy
Expand Down
60 changes: 33 additions & 27 deletions internal/archive/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,45 +42,51 @@ func Build(backupPaths ...string) (string, error) {
}

func buildArchiveLevels(tarArchive *tar.Writer, basePath, currentPath string) error {
files, err := ioutil.ReadDir(currentPath)
stat, err := os.Stat(currentPath)
if err != nil {
return fmt.Errorf("error reading path “%s”. details: %s", currentPath, err)
return fmt.Errorf("error retrieving path “%s” information. details: %s", currentPath, err)
}

for _, file := range files {
if file.IsDir() {
buildArchiveLevels(tarArchive, basePath, path.Join(currentPath, file.Name()))
continue
if stat.Mode().IsDir() {
files, err := ioutil.ReadDir(currentPath)
if err != nil {
return fmt.Errorf("error reading path “%s”. details: %s", currentPath, err)
}

tarHeader := tar.Header{
Name: path.Join(basePath, currentPath, file.Name()),
Mode: 0600,
Size: file.Size(),
ModTime: file.ModTime(),
for _, file := range files {
if err := buildArchiveLevels(tarArchive, basePath, path.Join(currentPath, file.Name())); err != nil {
return err
}
}

if err := tarArchive.WriteHeader(&tarHeader); err != nil {
return fmt.Errorf("error writing header in tar for file %s. details: %s", file.Name(), err)
}
return nil
}

filename := path.Join(currentPath, file.Name())
tarHeader := tar.Header{
Name: path.Join(basePath, currentPath),
Mode: 0600,
Size: stat.Size(),
ModTime: stat.ModTime(),
}

fd, err := os.Open(filename)
if err != nil {
return fmt.Errorf("error opening file %s. details: %s", filename, err)
}
if err := tarArchive.WriteHeader(&tarHeader); err != nil {
return fmt.Errorf("error writing header in tar for file %s. details: %s", stat.Name(), err)
}

if n, err := io.Copy(tarArchive, fd); err != nil {
return fmt.Errorf("error writing content in tar for file %s. details: %s", filename, err)
fd, err := os.Open(currentPath)
if err != nil {
return fmt.Errorf("error opening file %s. details: %s", currentPath, err)
}

} else if n != file.Size() {
return fmt.Errorf("wrong number of bytes written in file %s", filename)
}
if n, err := io.Copy(tarArchive, fd); err != nil {
return fmt.Errorf("error writing content in tar for file %s. details: %s", currentPath, err)

if err := fd.Close(); err != nil {
return fmt.Errorf("error closing file %s. details: %s", filename, err)
}
} else if n != stat.Size() {
return fmt.Errorf("wrong number of bytes written in file %s", currentPath)
}

if err := fd.Close(); err != nil {
return fmt.Errorf("error closing file %s. details: %s", currentPath, err)
}

return nil
Expand Down
157 changes: 146 additions & 11 deletions internal/archive/archive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestBuild(t *testing.T) {
expectedError error
}{
{
description: "it should create an archive correctly",
description: "it should create an archive correctly from directory path",
backupPaths: func() []string {
d, err := ioutil.TempDir("", "toglacier-test")
if err != nil {
Expand Down Expand Up @@ -97,28 +97,163 @@ func TestBuild(t *testing.T) {
return nil
},
},
{
description: "it should create an archive correctly from file path",
backupPaths: func() []string {
f, err := ioutil.TempFile("", "toglacier-test")
if err != nil {
t.Fatalf("error creating temporary directory. details %s", err)
}
defer f.Close()

f.WriteString("file test")

return []string{f.Name()}
}(),
expected: func(filename string) error {
f, err := os.Open(filename)
if err != nil {
return fmt.Errorf("error opening archive. details: %s", err)
}
defer f.Close()

basePath := path.Join(`backup-[0-9]+`, os.TempDir())
expectedFiles := []*regexp.Regexp{
regexp.MustCompile(`^` + path.Join(basePath, `toglacier-test[0-9]+`) + `$`),
}

tr := tar.NewReader(f)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}

if len(expectedFiles) == 0 {
return fmt.Errorf("content “%s” shouldn't be here", hdr.Name)
}

found := false
for i, expectedFile := range expectedFiles {
if expectedFile.MatchString(hdr.Name) {
expectedFiles = append(expectedFiles[:i], expectedFiles[i+1:]...)
found = true
break
}
}

if !found {
return fmt.Errorf("file “%s” did not match with any of the expected files", hdr.Name)
}
}

if len(expectedFiles) > 0 {
return errors.New("not all files were found in the archive")
}

return nil
},
},
{
description: "it should detect when the path does not exist",
backupPaths: func() []string {
return []string{"idontexist12345"}
}(),
expectedError: fmt.Errorf("error reading path “idontexist12345”. details: %s", &os.PathError{
Op: "open",
expectedError: fmt.Errorf("error retrieving path “idontexist12345” information. details: %s", &os.PathError{
Op: "stat",
Path: "idontexist12345",
Err: errors.New("no such file or directory"),
}),
},
{
description: "it should detect when the path (directory) does not have permission",
backupPaths: func() []string {
n := path.Join(os.TempDir(), "toglacier-test-archive-dir-noperm")
if _, err := os.Stat(n); os.IsNotExist(err) {
err := os.Mkdir(n, os.FileMode(0077))
if err != nil {
t.Fatalf("error creating a temporary directory. details: %s", err)
}
}

return []string{n}
}(),
expectedError: fmt.Errorf("error reading path “%s”. details: %s",
path.Join(os.TempDir(), "toglacier-test-archive-dir-noperm"),
&os.PathError{
Op: "open",
Path: path.Join(os.TempDir(), "toglacier-test-archive-dir-noperm"),
Err: errors.New("permission denied"),
}),
},
{
description: "it should detect when the path (file) does not have permission",
backupPaths: func() []string {
n := path.Join(os.TempDir(), "toglacier-test-archive-file-noperm")
if _, err := os.Stat(n); os.IsNotExist(err) {
f, err := os.OpenFile(n, os.O_CREATE, os.FileMode(0077))
if err != nil {
t.Fatalf("error creating a temporary file. details: %s", err)
}
defer f.Close()

f.WriteString("This is a test")
}

return []string{n}
}(),
expectedError: fmt.Errorf("error opening file %s. details: %s",
path.Join(os.TempDir(), "toglacier-test-archive-file-noperm"),
&os.PathError{
Op: "open",
Path: path.Join(os.TempDir(), "toglacier-test-archive-file-noperm"),
Err: errors.New("permission denied"),
}),
},
{
description: "it should detect an error while walking in the path",
backupPaths: func() []string {
n := path.Join(os.TempDir(), "toglacier-test-archive-dir-file-noperm")
if _, err := os.Stat(n); os.IsNotExist(err) {
err := os.Mkdir(n, os.FileMode(0700))
if err != nil {
t.Fatalf("error creating a temporary directory. details: %s", err)
}

f, err := os.OpenFile(path.Join(n, "file1"), os.O_CREATE, os.FileMode(0077))
if err != nil {
t.Fatalf("error creating a temporary file. details: %s", err)
}
defer f.Close()

f.WriteString("file1 test")
}

return []string{n}
}(),
expectedError: fmt.Errorf("error opening file %s. details: %s",
path.Join(os.TempDir(), "toglacier-test-archive-dir-file-noperm", "file1"),
&os.PathError{
Op: "open",
Path: path.Join(os.TempDir(), "toglacier-test-archive-dir-file-noperm", "file1"),
Err: errors.New("permission denied"),
}),
},
}

for _, scenario := range scenarios {
filename, err := archive.Build(scenario.backupPaths...)
if scenario.expectedError == nil && scenario.expected != nil {
if err := scenario.expected(filename); err != nil {
t.Errorf("unexpected archive content (%s). details: %s", filename, err)
t.Run(scenario.description, func(t *testing.T) {
filename, err := archive.Build(scenario.backupPaths...)
if scenario.expectedError == nil && scenario.expected != nil {
if err := scenario.expected(filename); err != nil {
t.Errorf("unexpected archive content (%s). details: %s", filename, err)
}
}
if !reflect.DeepEqual(scenario.expectedError, err) {
t.Errorf("errors don't match. expected “%v” and got “%v”", scenario.expectedError, err)
}
}
if !reflect.DeepEqual(scenario.expectedError, err) {
t.Errorf("errors don't match. expected “%v” and got “%v”", scenario.expectedError, err)
}
})
}
}
2 changes: 1 addition & 1 deletion toglacier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestBackup(t *testing.T) {
backupPaths: func() []string {
return []string{"idontexist12345"}
}(),
expectedLog: regexp.MustCompile(`[0-9]+/[0-9]+/[0-9]+ [0-9]+:[0-9]+:[0-9]+ error reading path “idontexist12345”. details: open idontexist12345: no such file or directory`),
expectedLog: regexp.MustCompile(`[0-9]+/[0-9]+/[0-9]+ [0-9]+:[0-9]+:[0-9]+ error retrieving path “idontexist12345” information. details: stat idontexist12345: no such file or directory`),
},
{
description: "it should detect an error while sending the backup",
Expand Down

0 comments on commit 67b285d

Please sign in to comment.