Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add technology parameter to filter copied files #4

Merged
merged 10 commits into from
Feb 7, 2025
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ The `dynatrace-bootstrapper` is a small CLI binary built into a [Dynatrace CodeM
- This is an **optional** arg
- The `--work` arg defines the base path for a tmp folder, this is where the command will do its work, to make sure the operations are atomic. It must be on the same disk as the target folder.

#### `--technology`

*Example*: `--technology="python,java"`

- This is an **optional** arg
- The `--technology` arg defines the paths associated to the given technology in the `manifest.json` file. Only those files will be copied that match the technology. It is a comma-separated list.

## Development

- To run tests: `make test`
Expand Down
46 changes: 46 additions & 0 deletions pkg/move/atomic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package move

import (
"os"

"github.com/sirupsen/logrus"
"github.com/spf13/afero"
)

func atomic(copy copyFunc) copyFunc {
return func(fs afero.Afero) error {
logrus.Infof("Starting to copy (atomic) from %s to %s", sourceFolder, targetFolder)

err := fs.RemoveAll(workFolder)
if err != nil {
logrus.Errorf("Failed initial cleanup of workdir: %v", err)

return err
}

err = fs.MkdirAll(workFolder, os.ModePerm)
if err != nil {
logrus.Errorf("Failed to create the base workdir: %v", err)

return err
}

defer func() {
err := fs.RemoveAll(workFolder)
if err != nil {
logrus.Errorf("Failed to do cleanup after run: %v", err)
}
}()

err = copy(fs)
if err != nil {
logrus.Errorf("Error moving folder: %v", err)

return err
}

logrus.Infof("Successfully copied from %s to %s", sourceFolder, targetFolder)

return nil
}
}
58 changes: 58 additions & 0 deletions pkg/move/atomic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package move

import (
"errors"
"testing"

"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
)

const tmpWorkFolder = "/work/tmp"

func mockCopyFunc(isSuccessful bool) copyFunc {
return func(fs afero.Afero) error {
if isSuccessful {
_ = fs.MkdirAll(tmpWorkFolder, 0755)
return nil
}

return errors.New("some mock error")
}
}
func TestAtomic(t *testing.T) {
t.Run("success -> targetFolder is present", func(t *testing.T) {
fs := afero.Afero{Fs: afero.NewMemMapFs()}
sourceFolder = "/source"
targetFolder = "/target"
workFolder = "/work"

err := fs.MkdirAll(sourceFolder, 0755)
assert.NoError(t, err)

atomicCopy := atomic(mockCopyFunc(true))

err = atomicCopy(fs)
assert.NoError(t, err)

exists, err := fs.DirExists(workFolder)
assert.NoError(t, err)
assert.False(t, exists)
})
t.Run("fail -> targetFolder is not present", func(t *testing.T) {
fs := afero.Afero{Fs: afero.NewMemMapFs()}
sourceFolder = "/source"
targetFolder = "/target"
workFolder = "/work"

atomicCopy := atomic(mockCopyFunc(false))

err := atomicCopy(fs)
assert.Error(t, err)
assert.Equal(t, "some mock error", err.Error())

exists, err := fs.DirExists(workFolder)
assert.NoError(t, err)
assert.False(t, exists)
})
}
155 changes: 11 additions & 144 deletions pkg/move/cmd.go
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
package move

import (
"io"
"os"
"path/filepath"

"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)
Expand All @@ -15,14 +9,14 @@ const (
sourceFolderFlag = "source"
targetFolderFlag = "target"
workFolderFlag = "work"

copyTmpFolder = "copy-tmp"
technologyFlag = "technology"
)

var (
sourceFolder string
targetFolder string
workFolder string
technology string
)

func AddFlags(cmd *cobra.Command) {
Expand All @@ -33,150 +27,23 @@ func AddFlags(cmd *cobra.Command) {
_ = cmd.MarkPersistentFlagRequired(targetFolderFlag)

cmd.PersistentFlags().StringVar(&workFolder, workFolderFlag, "", "(Optional) Base path for a tmp folder, this is where the command will do its work, to make sure the operations are atomic. It must be on the same disk as the target folder.")

cmd.PersistentFlags().StringVar(&technology, technologyFlag, "", "(Optional) Comma-separated list of technologies to filter files.")

}

// Execute moves the contents of a folder to another via copying.
// This could be a simple os.Rename, however that will not work if the source and target are on different disk.
func Execute(fs afero.Afero) error {
if workFolder != "" {
return atomicCopy(fs)
}

return simpleCopy(fs)
}

func atomicCopy(fs afero.Afero) error {
logrus.Infof("Starting to copy (atomic) from %s to %s", sourceFolder, targetFolder)

err := fs.RemoveAll(workFolder)
if err != nil {
logrus.Errorf("Failed initial cleanup of workdir: %v", err)

return err
}

err = fs.MkdirAll(workFolder, os.ModePerm)
if err != nil {
logrus.Errorf("Failed to create the base workdir: %v", err)

return err
}

defer func() {
err := fs.RemoveAll(workFolder)
if err != nil {
logrus.Errorf("Failed to do cleanup after run: %v", err)
}
}()

tmpFolder := filepath.Join(workFolder, copyTmpFolder)

err = copyFolder(fs, sourceFolder, tmpFolder)
if err != nil {
logrus.Errorf("Error moving folder: %v", err)

return err
}

err = fs.Rename(tmpFolder, targetFolder)
if err != nil {
logrus.Errorf("Error finalizing move: %v", err)

return err
}

logrus.Infof("Successfully copied from %s to %s", sourceFolder, targetFolder)

return nil
}

func simpleCopy(fs afero.Afero) error {
logrus.Infof("Starting to copy (simple) from %s to %s", sourceFolder, targetFolder)

err := copyFolder(fs, sourceFolder, targetFolder)
if err != nil {
logrus.Errorf("Error moving folder: %v", err)
copy := simpleCopy

return err
if technology != "" {
copy = copyByTechnology
}

logrus.Infof("Successfully copied from %s to %s", sourceFolder, targetFolder)

return nil
}

func copyFolder(fs afero.Fs, from string, to string) error {
fromInfo, err := fs.Stat(from)
if err != nil {
return errors.WithStack(err)
}

if !fromInfo.IsDir() {
return errors.Errorf("%s is not a directory", from)
}

err = fs.MkdirAll(to, fromInfo.Mode())
if err != nil {
return errors.WithStack(err)
}

entries, err := afero.ReadDir(fs, from)
if err != nil {
return errors.WithStack(err)
}

for _, entry := range entries {
toPath := filepath.Join(from, entry.Name())
fromPath := filepath.Join(to, entry.Name())

if entry.IsDir() {
logrus.Infof("Copying directory %s to %s", toPath, fromPath)

err = copyFolder(fs, toPath, fromPath)
if err != nil {
return err
}
} else {
logrus.Infof("Copying file %s to %s", toPath, fromPath)

err = copyFile(fs, toPath, fromPath)
if err != nil {
return err
}
}
}

return nil
}

func copyFile(fs afero.Fs, sourcePath string, destinationPath string) error {
sourceFile, err := fs.Open(sourcePath)
if err != nil {
return errors.WithStack(err)
}
defer sourceFile.Close()

sourceInfo, err := sourceFile.Stat()
if err != nil {
return errors.WithStack(err)
}

destinationFile, err := fs.OpenFile(destinationPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, sourceInfo.Mode())
if err != nil {
return errors.WithStack(err)
}

defer destinationFile.Close()

_, err = io.Copy(destinationFile, sourceFile)
if err != nil {
return errors.WithStack(err)
}

err = destinationFile.Sync()
if err != nil {
return errors.WithStack(err)
if workFolder != "" {
copy = atomic(copy)
}

return nil
return copy(fs)
}
Loading