diff --git a/util/file.go b/util/file.go index a76205744..782e128d0 100644 --- a/util/file.go +++ b/util/file.go @@ -1,6 +1,7 @@ package util import ( + "bytes" "encoding/gob" "io" "os" @@ -584,15 +585,47 @@ func (err PathIsNotFile) Error() string { // Terraform 0.14 now generates a lock file when you run `terraform init`. // If any such file exists, this function will copy the lock file to the destination folder func CopyLockFile(sourceFolder string, destinationFolder string, logger *logrus.Entry) error { - sourceLockFilePath := JoinPath(sourceFolder, TerraformLockFile) - destinationLockFilePath := JoinPath(destinationFolder, TerraformLockFile) + sourceLockFilePath, sourceErr := filepath.Abs(JoinPath(sourceFolder, TerraformLockFile)) + if sourceErr != nil { + return errors.WithStackTrace(sourceErr) + } + destinationLockFilePath, destErr := filepath.Abs(JoinPath(destinationFolder, TerraformLockFile)) + if destErr != nil { + return errors.WithStackTrace(destErr) + } - if FileExists(sourceLockFilePath) { - logger.Debugf("Copying lock file from %s to %s", sourceLockFilePath, destinationFolder) - return CopyFile(sourceLockFilePath, destinationLockFilePath) + if sourceLockFilePath == destinationLockFilePath { + logger.Debugf("Source and destination lock file paths are the same: %s. Not copying.", sourceLockFilePath) + return nil } - return nil + if !FileExists(sourceLockFilePath) { + logger.Debugf("Source lock file does not exist: %s. Not copying.", sourceLockFilePath) + return nil + } + + sourceContents, sourceReadErr := os.ReadFile(sourceLockFilePath) + if sourceReadErr != nil { + return errors.WithStackTrace(sourceReadErr) + } + + if !FileExists(destinationLockFilePath) { + logger.Debugf("Destination lock file does not exist: %s. Copying.", destinationLockFilePath) + return WriteFileWithSamePermissions(sourceLockFilePath, destinationLockFilePath, sourceContents) + } + + destinationContents, destReadErr := os.ReadFile(destinationLockFilePath) + if destReadErr != nil { + return errors.WithStackTrace(destReadErr) + } + + if bytes.Equal(sourceContents, destinationContents) { + logger.Debugf("Source and destination lock file contents are the same. Not copying.") + return nil + } + + logger.Debugf("Copying lock file from %s to %s", sourceLockFilePath, destinationFolder) + return WriteFileWithSamePermissions(sourceLockFilePath, destinationLockFilePath, sourceContents) } // ListTfFiles returns a list of all TF files in the specified directory. diff --git a/util/file_test.go b/util/file_test.go index 2e032c435..3a5ecda72 100644 --- a/util/file_test.go +++ b/util/file_test.go @@ -2,6 +2,8 @@ package util import ( "errors" + "io" + "io/ioutil" "os" "path" "path/filepath" @@ -11,6 +13,7 @@ import ( "fmt" "github.com/gruntwork-io/terragrunt/test/helpers" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -336,5 +339,116 @@ func TestEmptyDir(t *testing.T) { assert.NoError(t, err) assert.Equal(t, testCase.expectEmpty, emptyValue, "For path %s", testCase.path) } +} + +func TestCopyLockFile(t *testing.T) { + logger := logrus.New() + logger.Out = io.Discard + loggerEntry := logger.WithFields(logrus.Fields{}) + + t.Run("SameSourceAndDestination", func(t *testing.T) { + sourceFolder := "/path/to/folder" + destinationFolder := "/path/to/folder" + + err := CopyLockFile(sourceFolder, destinationFolder, loggerEntry) + assert.NoError(t, err) + }) + + t.Run("SourceLockFileDoesNotExist", func(t *testing.T) { + sourceFolder := "/path/to/folder" + destinationFolder := "/path/to/destination" + + err := CopyLockFile(sourceFolder, destinationFolder, loggerEntry) + assert.NoError(t, err) + }) + + t.Run("DestinationLockFileDoesNotExist", func(t *testing.T) { + sourceFolder := t.TempDir() + destinationFolder := t.TempDir() + + sourceLockFilePath := filepath.Join(sourceFolder, TerraformLockFile) + destinationLockFilePath := filepath.Join(destinationFolder, TerraformLockFile) + + // Create source lock file + err := ioutil.WriteFile(sourceLockFilePath, []byte("lock file contents"), 0644) + require.NoError(t, err) + + err = CopyLockFile(sourceFolder, destinationFolder, loggerEntry) + assert.NoError(t, err) + + // Verify destination lock file exists + _, err = os.Stat(destinationLockFilePath) + assert.NoError(t, err) + + // Verify destination lock file contents + destinationContents, err := ioutil.ReadFile(destinationLockFilePath) + assert.NoError(t, err) + assert.Equal(t, []byte("lock file contents"), destinationContents) + + // Clean up + err = os.Remove(sourceLockFilePath) + assert.NoError(t, err) + err = os.Remove(destinationLockFilePath) + assert.NoError(t, err) + }) + + t.Run("SameContents", func(t *testing.T) { + sourceFolder := t.TempDir() + destinationFolder := t.TempDir() + sourceLockFilePath := filepath.Join(sourceFolder, TerraformLockFile) + destinationLockFilePath := filepath.Join(destinationFolder, TerraformLockFile) + + // Create source lock file + err := ioutil.WriteFile(sourceLockFilePath, []byte("lock file contents"), 0644) + require.NoError(t, err) + + // Create destination lock file with same contents + err = ioutil.WriteFile(destinationLockFilePath, []byte("lock file contents"), 0644) + require.NoError(t, err) + + err = CopyLockFile(sourceFolder, destinationFolder, loggerEntry) + assert.NoError(t, err) + + // Verify destination lock file contents remain the same + destinationContents, err := ioutil.ReadFile(destinationLockFilePath) + assert.NoError(t, err) + assert.Equal(t, []byte("lock file contents"), destinationContents) + + // Clean up + err = os.Remove(sourceLockFilePath) + assert.NoError(t, err) + err = os.Remove(destinationLockFilePath) + assert.NoError(t, err) + }) + + t.Run("DifferentContents", func(t *testing.T) { + sourceFolder := t.TempDir() + destinationFolder := t.TempDir() + + sourceLockFilePath := filepath.Join(sourceFolder, TerraformLockFile) + destinationLockFilePath := filepath.Join(destinationFolder, TerraformLockFile) + + // Create source lock file + err := ioutil.WriteFile(sourceLockFilePath, []byte("lock file contents"), 0644) + require.NoError(t, err) + + // Create destination lock file with different contents + err = ioutil.WriteFile(destinationLockFilePath, []byte("different contents"), 0644) + require.NoError(t, err) + + err = CopyLockFile(sourceFolder, destinationFolder, loggerEntry) + assert.NoError(t, err) + + // Verify destination lock file contents are updated + destinationContents, err := ioutil.ReadFile(destinationLockFilePath) + assert.NoError(t, err) + assert.Equal(t, []byte("lock file contents"), destinationContents) + + // Clean up + err = os.Remove(sourceLockFilePath) + assert.NoError(t, err) + err = os.Remove(destinationLockFilePath) + assert.NoError(t, err) + }) }