mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
feat(stacks): support automated sync for stacks [EE-248] (#5340)
This commit is contained in:
parent
5fe90db36a
commit
bcccdfb669
94 changed files with 2680 additions and 469 deletions
82
api/filesystem/copy.go
Normal file
82
api/filesystem/copy.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package filesystem
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CopyPath copies file or directory defined by the path to the toDir path
|
||||
func CopyPath(path string, toDir string) error {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||
// skip copy if file does not exist
|
||||
return nil
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
destination := filepath.Join(toDir, info.Name())
|
||||
return copyFile(path, destination)
|
||||
}
|
||||
|
||||
return CopyDir(path, toDir, true)
|
||||
}
|
||||
|
||||
// CopyDir copies contents of fromDir to toDir.
|
||||
// When keepParent is true, contents will be copied with their immediate parent dir,
|
||||
// i.e. given /from/dirA and /to/dirB with keepParent == true, result will be /to/dirB/dirA/<children>
|
||||
func CopyDir(fromDir, toDir string, keepParent bool) error {
|
||||
cleanedSourcePath := filepath.Clean(fromDir)
|
||||
parentDirectory := filepath.Dir(cleanedSourcePath)
|
||||
err := filepath.Walk(cleanedSourcePath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var destination string
|
||||
if keepParent {
|
||||
destination = filepath.Join(toDir, strings.TrimPrefix(path, parentDirectory))
|
||||
} else {
|
||||
destination = filepath.Join(toDir, strings.TrimPrefix(path, cleanedSourcePath))
|
||||
}
|
||||
|
||||
if destination == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil // skip directory creations
|
||||
}
|
||||
|
||||
if info.Mode()&os.ModeSymlink != 0 { // entry is a symlink
|
||||
return nil // don't copy symlinks
|
||||
}
|
||||
|
||||
return copyFile(path, destination)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// copies regular a file from src to dst
|
||||
func copyFile(src, dst string) error {
|
||||
from, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer from.Close()
|
||||
|
||||
// has to include 'execute' bit, otherwise fails. MkdirAll follows `mkdir -m` restrictions
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0744); err != nil {
|
||||
return err
|
||||
}
|
||||
to, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer to.Close()
|
||||
|
||||
_, err = io.Copy(to, from)
|
||||
return err
|
||||
}
|
92
api/filesystem/copy_test.go
Normal file
92
api/filesystem/copy_test.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
package filesystem
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_copyFile_returnsError_whenSourceDoesNotExist(t *testing.T) {
|
||||
tmpdir, _ := ioutil.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
err := copyFile("does-not-exist", tmpdir)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func Test_copyFile_shouldMakeAbackup(t *testing.T) {
|
||||
tmpdir, _ := ioutil.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(path.Join(tmpdir, "origin"), content, 0600)
|
||||
|
||||
err := copyFile(path.Join(tmpdir, "origin"), path.Join(tmpdir, "copy"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
copyContent, _ := ioutil.ReadFile(path.Join(tmpdir, "copy"))
|
||||
assert.Equal(t, content, copyContent)
|
||||
}
|
||||
|
||||
func Test_CopyDir_shouldCopyAllFilesAndDirectories(t *testing.T) {
|
||||
destination, _ := ioutil.TempDir("", "destination")
|
||||
defer os.RemoveAll(destination)
|
||||
err := CopyDir("./testdata/copy_test", destination, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.FileExists(t, filepath.Join(destination, "copy_test", "outer"))
|
||||
assert.FileExists(t, filepath.Join(destination, "copy_test", "dir", ".dotfile"))
|
||||
assert.FileExists(t, filepath.Join(destination, "copy_test", "dir", "inner"))
|
||||
}
|
||||
|
||||
func Test_CopyDir_shouldCopyOnlyDirContents(t *testing.T) {
|
||||
destination, _ := ioutil.TempDir("", "destination")
|
||||
defer os.RemoveAll(destination)
|
||||
err := CopyDir("./testdata/copy_test", destination, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.FileExists(t, filepath.Join(destination, "outer"))
|
||||
assert.FileExists(t, filepath.Join(destination, "dir", ".dotfile"))
|
||||
assert.FileExists(t, filepath.Join(destination, "dir", "inner"))
|
||||
}
|
||||
|
||||
func Test_CopyPath_shouldSkipWhenNotExist(t *testing.T) {
|
||||
tmpdir, _ := ioutil.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
err := CopyPath("does-not-exists", tmpdir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoFileExists(t, tmpdir)
|
||||
}
|
||||
|
||||
func Test_CopyPath_shouldCopyFile(t *testing.T) {
|
||||
tmpdir, _ := ioutil.TempDir("", "backup")
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
content := []byte("content")
|
||||
ioutil.WriteFile(path.Join(tmpdir, "file"), content, 0600)
|
||||
|
||||
os.MkdirAll(path.Join(tmpdir, "backup"), 0700)
|
||||
err := CopyPath(path.Join(tmpdir, "file"), path.Join(tmpdir, "backup"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
copyContent, err := ioutil.ReadFile(path.Join(tmpdir, "backup", "file"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, content, copyContent)
|
||||
}
|
||||
|
||||
func Test_CopyPath_shouldCopyDir(t *testing.T) {
|
||||
destination, _ := ioutil.TempDir("", "destination")
|
||||
defer os.RemoveAll(destination)
|
||||
err := CopyPath("./testdata/copy_test", destination)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.FileExists(t, filepath.Join(destination, "copy_test", "outer"))
|
||||
assert.FileExists(t, filepath.Join(destination, "copy_test", "dir", ".dotfile"))
|
||||
assert.FileExists(t, filepath.Join(destination, "copy_test", "dir", "inner"))
|
||||
}
|
1
api/filesystem/testdata/copy_test/dir/.dotfile
vendored
Normal file
1
api/filesystem/testdata/copy_test/dir/.dotfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
content
|
1
api/filesystem/testdata/copy_test/dir/inner
vendored
Normal file
1
api/filesystem/testdata/copy_test/dir/inner
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
content
|
1
api/filesystem/testdata/copy_test/outer
vendored
Normal file
1
api/filesystem/testdata/copy_test/outer
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
content
|
Loading…
Add table
Add a link
Reference in a new issue