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

Fix Vendoring Issues with Globs and Symlinks #984

Open
wants to merge 84 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
ee60848
symlinks
Listener430 Jan 29, 2025
940d876
comment
Listener430 Jan 29, 2025
16a72f3
log warning
Listener430 Jan 29, 2025
f9b3348
adding back the rest getters
Listener430 Jan 29, 2025
ed1af3d
fix for windows path
Listener430 Jan 30, 2025
f920318
Merge branch 'main' into DEV-2964
Listener430 Jan 31, 2025
5a6789e
globs
Listener430 Feb 1, 2025
b8d7a5a
Merge branch 'main' into DEV-2964
Listener430 Feb 3, 2025
e96dd7f
removing atmosconfig from logging
Listener430 Feb 3, 2025
06f5f3b
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 3, 2025
82caa05
Update internal/exec/copy_glob.go
Listener430 Feb 6, 2025
b9d4e5a
logging to charmbracelet
Listener430 Feb 6, 2025
0b8e5fa
renaming skipFunc
Listener430 Feb 6, 2025
e8133be
Update internal/exec/go_getter_utils.go
Listener430 Feb 6, 2025
d390d80
Merge branch 'main' into DEV-2964
Listener430 Feb 6, 2025
8479409
new testcase
Listener430 Feb 6, 2025
c4adfb2
added depth=1
Listener430 Feb 6, 2025
7382cab
depth
Listener430 Feb 6, 2025
a9c8f48
Update internal/exec/go_getter_utils.go
Listener430 Feb 6, 2025
a54dafb
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 6, 2025
41dbf88
Merge branch 'main' into DEV-2964
osterman Feb 7, 2025
2bf586a
excluded subfolders
Listener430 Feb 7, 2025
17db3ed
Merge branch 'DEV-2964' of https://github.com/cloudposse/atmos into D…
Listener430 Feb 7, 2025
1fea6ed
shallow copy
Listener430 Feb 8, 2025
618860d
docs added
Listener430 Feb 10, 2025
6db3458
Merge branch 'main' into DEV-2964
osterman Feb 10, 2025
d2533a7
Update website/docs/core-concepts/vendor/vendor-manifest.mdx
Listener430 Feb 11, 2025
420741e
Merge branch 'main' into DEV-2964
osterman Feb 11, 2025
ae1fb21
Merge branch 'main' into DEV-2964
aknysh Feb 11, 2025
7e7384f
correct ssh-style github URLs that use semicolon as a separator
Listener430 Feb 12, 2025
b06181e
degug verbose removal
Listener430 Feb 12, 2025
d330de3
reverting the recent changes
Listener430 Feb 13, 2025
e342352
Merge branch 'main' into DEV-2964
osterman Feb 20, 2025
d05a1de
Update website/docs/core-concepts/vendor/vendor-manifest.mdx
Listener430 Feb 22, 2025
be1c8ce
Update website/docs/core-concepts/vendor/vendor-manifest.mdx
Listener430 Feb 22, 2025
a08f63e
Update website/docs/core-concepts/vendor/vendor-manifest.mdx
Listener430 Feb 22, 2025
10118a1
Update website/docs/core-concepts/vendor/vendor-manifest.mdx
Listener430 Feb 22, 2025
89827fd
Update website/docs/core-concepts/vendor/vendor-manifest.mdx
Listener430 Feb 22, 2025
4c51104
new testcases
Listener430 Feb 22, 2025
d854d4b
tests/golangci
Listener430 Feb 22, 2025
1882f98
more tests/golangci
Listener430 Feb 22, 2025
b839cd4
globs refactoring
Listener430 Feb 26, 2025
deb228c
refactoring globs
Listener430 Feb 26, 2025
9a75bd4
atmosConfig is now a pointer in GoGetter
Listener430 Feb 26, 2025
42ff520
cyclomatic counter temp bump to 13
Listener430 Feb 26, 2025
f8e949e
remaining linter feedback is fixed
Listener430 Feb 26, 2025
eb8b7fa
linter fix
Listener430 Feb 26, 2025
086c79b
symmlynk debug
Listener430 Feb 26, 2025
6c4ae3c
Merge branch 'main' into DEV-2964
osterman Feb 27, 2025
b1b5ed4
Merge branch 'main' into DEV-2964
osterman Feb 28, 2025
efd272f
Merge branch 'main' into DEV-2964
osterman Mar 1, 2025
ec93039
Update .gitignore
Listener430 Mar 2, 2025
07f5ad7
Merge branch 'main' into DEV-2964
osterman Mar 2, 2025
4b88dfc
unit tests for copy_glob.go
Listener430 Mar 3, 2025
2be732e
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 3, 2025
9b4fd73
remaining unit tests for copy_glob.go
Listener430 Mar 3, 2025
9ab297a
Merge branch 'DEV-2964' of github.com:cloudposse/atmos into DEV-2964
Listener430 Mar 3, 2025
ac526c4
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 3, 2025
bcc852b
os.WriteFile to use 0o600 permission instead of 0o644
Listener430 Mar 3, 2025
2c63916
Merge branch 'DEV-2964' of github.com:cloudposse/atmos into DEV-2964
Listener430 Mar 3, 2025
5bbfe66
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 3, 2025
ea198e1
more copy_glob tests
Listener430 Mar 3, 2025
68642ed
missing test added
Listener430 Mar 3, 2025
417f19c
more copy_glob tests
Listener430 Mar 4, 2025
de2e261
linter feedback implementation
Listener430 Mar 4, 2025
4d5eb89
Single line function expansion to pass gofmt
Listener430 Mar 4, 2025
88ae6e2
more linter fixes
Listener430 Mar 4, 2025
36ce7ed
Merge branch 'main' into DEV-2964
osterman Mar 4, 2025
626c5f3
Merge branch 'main' into DEV-2964
jamengual Mar 5, 2025
9e841ac
magic numbers, constnants and upgraded error messaging for vendor_mod…
Listener430 Mar 6, 2025
daf627f
more linter feedback
Listener430 Mar 6, 2025
3f88910
more linter feedback for vendor model and vendor model component
Listener430 Mar 6, 2025
4bd24fd
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 6, 2025
79ab2a6
more linter fixes
Listener430 Mar 6, 2025
84f5810
Merge branch 'DEV-2964' of https://github.com/cloudposse/atmos into D…
Listener430 Mar 6, 2025
564e0d7
Refactored error handling, replaced deprecated logging, and removed u…
Listener430 Mar 6, 2025
c864838
moving more errors to errors.go and fixing other linter issues
Listener430 Mar 6, 2025
c7764b8
updated errors.go
Listener430 Mar 6, 2025
225b0f1
atmosConfig is no longer passed by value in vendor model
Listener430 Mar 6, 2025
2b04482
More atmosConfig references
Listener430 Mar 6, 2025
1e149f1
Merge branch 'main' into DEV-2964
Listener430 Mar 7, 2025
8e0f8a3
Merge branch 'main' into DEV-2964
osterman Mar 7, 2025
4e640c9
Merge branch 'main' into DEV-2964
osterman Mar 7, 2025
447a305
Merge branch 'main' into DEV-2964
osterman Mar 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 255 additions & 0 deletions internal/exec/copy_glob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
package exec

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

"github.com/cloudposse/atmos/pkg/schema"
u "github.com/cloudposse/atmos/pkg/utils"
cp "github.com/otiai10/copy" // Using the optimized copy library when no filtering is required.
)

// copyFile copies a single file from src to dst while preserving file permissions.
func copyFile(atmosConfig schema.AtmosConfiguration, src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("opening source file %q: %w", src, err)
}
defer sourceFile.Close()

if err := os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {
return fmt.Errorf("creating destination directory for %q: %w", dst, err)
}

destinationFile, err := os.Create(dst)
if err != nil {
return fmt.Errorf("creating destination file %q: %w", dst, err)
}
defer destinationFile.Close()

if _, err := io.Copy(destinationFile, sourceFile); err != nil {
return fmt.Errorf("copying content from %q to %q: %w", src, dst, err)
}

info, err := os.Stat(src)
if err != nil {
return fmt.Errorf("getting file info for %q: %w", src, err)
}
if err := os.Chmod(dst, info.Mode()); err != nil {
return fmt.Errorf("setting permissions on %q: %w", dst, err)
}
return nil
}

// skipFunc determines whether to skip a file/directory based on its relative path to baseDir.
// If an error occurs during matching for an exclusion or inclusion pattern, it logs the error and proceeds.
func skipFunc(atmosConfig schema.AtmosConfiguration, info os.FileInfo, srcPath, baseDir string, excluded, included []string) (bool, error) {
if info.Name() == ".git" {
return true, nil
}
relPath, err := filepath.Rel(baseDir, srcPath)
if err != nil {
u.LogTrace(atmosConfig, fmt.Sprintf("Error computing relative path for %q: %v", srcPath, err))

Check failure on line 55 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 55 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 55 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace

Check failure on line 55 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 55 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 55 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace
return true, nil // treat error as a signal to skip
}
relPath = filepath.ToSlash(relPath)

// Process exclusion patterns.
for _, pattern := range excluded {
matched, err := u.PathMatch(pattern, relPath)
if err != nil {
u.LogTrace(atmosConfig, fmt.Sprintf("Error matching exclusion pattern %q with %q: %v", pattern, relPath, err))

Check failure on line 64 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 64 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 64 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace

Check failure on line 64 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 64 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 64 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace
continue
} else if matched {
u.LogTrace(atmosConfig, fmt.Sprintf("Excluding %q because it matches exclusion pattern %q", relPath, pattern))

Check failure on line 67 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 67 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 67 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace

Check failure on line 67 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 67 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 67 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace
return true, nil
}
}

// Process inclusion patterns (only for non-directory files).
if len(included) > 0 && !info.IsDir() {
matchedAny := false
for _, pattern := range included {
matched, err := u.PathMatch(pattern, relPath)
if err != nil {
u.LogTrace(atmosConfig, fmt.Sprintf("Error matching inclusion pattern %q with %q: %v", pattern, relPath, err))

Check failure on line 78 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 78 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 78 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace

Check failure on line 78 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 78 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 78 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace
continue
} else if matched {
u.LogTrace(atmosConfig, fmt.Sprintf("Including %q because it matches inclusion pattern %q", relPath, pattern))

Check failure on line 81 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 81 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 81 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace

Check failure on line 81 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 81 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 81 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace
matchedAny = true
break
}
}
if !matchedAny {
u.LogTrace(atmosConfig, fmt.Sprintf("Excluding %q because it does not match any inclusion pattern", relPath))

Check failure on line 87 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 87 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 87 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace

Check failure on line 87 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 87 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 87 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace
return true, nil
}
}
return false, nil
}

// copyDirRecursive recursively copies srcDir to dstDir using skipFunc filtering.
func copyDirRecursive(atmosConfig schema.AtmosConfiguration, srcDir, dstDir, baseDir string, excluded, included []string) error {
entries, err := os.ReadDir(srcDir)
if err != nil {
return fmt.Errorf("reading directory %q: %w", srcDir, err)
}
for _, entry := range entries {
srcPath := filepath.Join(srcDir, entry.Name())
dstPath := filepath.Join(dstDir, entry.Name())

info, err := entry.Info()
if err != nil {
return fmt.Errorf("getting info for %q: %w", srcPath, err)
}

skip, err := skipFunc(atmosConfig, info, srcPath, baseDir, excluded, included)
if err != nil {
return err
}
if skip {
continue
}

// Skip symlinks.
if info.Mode()&os.ModeSymlink != 0 {
u.LogTrace(atmosConfig, fmt.Sprintf("Skipping symlink: %q", srcPath))

Check failure on line 119 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 119 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 119 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace

Check failure on line 119 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 119 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 119 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace
continue
}

if info.IsDir() {
if err := os.MkdirAll(dstPath, info.Mode()); err != nil {
return fmt.Errorf("creating directory %q: %w", dstPath, err)
}
if err := copyDirRecursive(atmosConfig, srcPath, dstPath, baseDir, excluded, included); err != nil {
return err
}
} else {
if err := copyFile(atmosConfig, srcPath, dstPath); err != nil {
return err
}
}
}
return nil
}

// getMatchesForPattern returns files/directories matching a pattern relative to sourceDir.
// If no matches are found, it logs a trace and returns an empty slice.
// When the pattern ends with "/*", it retries with a recursive "/**" variant.
func getMatchesForPattern(atmosConfig schema.AtmosConfiguration, sourceDir, pattern string) ([]string, error) {
fullPattern := filepath.Join(sourceDir, pattern)
matches, err := u.GetGlobMatches(fullPattern)
if err != nil {
return nil, fmt.Errorf("error getting glob matches for %q: %w", fullPattern, err)
}
if len(matches) == 0 {
if strings.HasSuffix(pattern, "/*") {
recursivePattern := strings.TrimSuffix(pattern, "/*") + "/**"
fullRecursivePattern := filepath.Join(sourceDir, recursivePattern)
matches, err = u.GetGlobMatches(fullRecursivePattern)
if err != nil {
return nil, fmt.Errorf("error getting glob matches for recursive pattern %q: %w", fullRecursivePattern, err)
}
if len(matches) == 0 {
u.LogTrace(atmosConfig, fmt.Sprintf("No matches found for recursive pattern %q - target directory will be empty", fullRecursivePattern))

Check failure on line 157 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 157 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 157 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace

Check failure on line 157 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 157 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 157 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace
return []string{}, nil
}
return matches, nil
}
u.LogTrace(atmosConfig, fmt.Sprintf("No matches found for pattern %q - target directory will be empty", fullPattern))

Check failure on line 162 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 162 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 162 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace

Check failure on line 162 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 162 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 162 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace
return []string{}, nil
}
return matches, nil
}

// copyToTargetWithPatterns copies the contents from sourceDir to targetPath,
// applying inclusion and exclusion patterns from the vendor source configuration.
// If sourceIsLocalFile is true and targetPath lacks an extension, the sanitized URI is appended.
// If no included paths are defined, all files (except those matching excluded paths) are copied.
// In the special case where neither inclusion nor exclusion patterns are defined,
// the optimized cp library (github.com/otiai10/copy) is used.
func copyToTargetWithPatterns(
atmosConfig schema.AtmosConfiguration,
sourceDir, targetPath string,
s *schema.AtmosVendorSource,
sourceIsLocalFile bool,
uri string,
) error {
if sourceIsLocalFile && filepath.Ext(targetPath) == "" {
targetPath = filepath.Join(targetPath, SanitizeFileName(uri))
}
u.LogTrace(atmosConfig, fmt.Sprintf("Copying from %q to %q", sourceDir, targetPath))

Check failure on line 184 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 184 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 184 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace

Check failure on line 184 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

too many arguments in call to u.LogTrace

Check failure on line 184 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

too many arguments in call to u.LogTrace

Check failure on line 184 in internal/exec/copy_glob.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

too many arguments in call to u.LogTrace
if err := os.MkdirAll(targetPath, os.ModePerm); err != nil {
return fmt.Errorf("creating target directory %q: %w", targetPath, err)
}

// Optimization: if no inclusion and no exclusion patterns are defined, use the cp library for fast copying.
if len(s.IncludedPaths) == 0 && len(s.ExcludedPaths) == 0 {
u.LogTrace(atmosConfig, "No inclusion or exclusion patterns defined; using cp library for fast copy")
return cp.Copy(sourceDir, targetPath)
}

// If inclusion patterns are provided, use them to determine which files to copy.
if len(s.IncludedPaths) > 0 {
filesToCopy := make(map[string]struct{})
for _, pattern := range s.IncludedPaths {
matches, err := getMatchesForPattern(atmosConfig, sourceDir, pattern)
if err != nil {
u.LogTrace(atmosConfig, fmt.Sprintf("Warning: error getting matches for pattern %q: %v", pattern, err))
continue
}
for _, match := range matches {
filesToCopy[match] = struct{}{}
}
}
if len(filesToCopy) == 0 {
u.LogTrace(atmosConfig, "No files matched the inclusion patterns - target directory will be empty")
return nil
}
for file := range filesToCopy {
relPath, err := filepath.Rel(sourceDir, file)
if err != nil {
return fmt.Errorf("computing relative path for %q: %w", file, err)
}
relPath = filepath.ToSlash(relPath)
skip := false
for _, ex := range s.ExcludedPaths {
matched, err := u.PathMatch(ex, relPath)
if err != nil {
u.LogTrace(atmosConfig, fmt.Sprintf("Error matching exclusion pattern %q with %q: %v", ex, relPath, err))
continue
} else if matched {
u.LogTrace(atmosConfig, fmt.Sprintf("Excluding %q because it matches exclusion pattern %q", relPath, ex))
skip = true
break
}
}
if skip {
continue
}
dstPath := filepath.Join(targetPath, relPath)
info, err := os.Stat(file)
if err != nil {
return fmt.Errorf("stating file %q: %w", file, err)
}
if info.IsDir() {
if err := copyDirRecursive(atmosConfig, file, dstPath, file, s.ExcludedPaths, nil); err != nil {
return err
}
} else {
if err := copyFile(atmosConfig, file, dstPath); err != nil {
return err
}
}
}
} else {
// No inclusion patterns defined; copy everything except those matching excluded items.
if err := copyDirRecursive(atmosConfig, sourceDir, targetPath, sourceDir, s.ExcludedPaths, s.IncludedPaths); err != nil {
return fmt.Errorf("error copying from %q to %q: %w", sourceDir, targetPath, err)
}
}
return nil
}
63 changes: 58 additions & 5 deletions internal/exec/go_getter_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func IsValidScheme(scheme string) bool {
// do a git-based clone with a token.
type CustomGitHubDetector struct {
AtmosConfig schema.AtmosConfiguration
source string
}

// Detect implements the getter.Detector interface for go-getter v1.
Expand Down Expand Up @@ -99,6 +100,14 @@ func (d *CustomGitHubDetector) Detect(src, _ string) (string, bool, error) {
return "", false, fmt.Errorf("invalid GitHub URL %q", parsedURL.Path)
}

if !strings.Contains(d.source, "//") {
// means user typed something like "github.com/org/repo.git" with NO subdir
if strings.HasSuffix(parsedURL.Path, ".git") || len(parts) == 3 {
u.LogDebug(d.AtmosConfig, "Detected top-level repo with no subdir: appending '//.'\n")
parsedURL.Path = parsedURL.Path + "//."
}
}

atmosGitHubToken := os.Getenv("ATMOS_GITHUB_TOKEN")
gitHubToken := os.Getenv("GITHUB_TOKEN")

Expand Down Expand Up @@ -139,10 +148,10 @@ func (d *CustomGitHubDetector) Detect(src, _ string) (string, bool, error) {

// RegisterCustomDetectors prepends the custom detector so it runs before
// the built-in ones. Any code that calls go-getter should invoke this.
func RegisterCustomDetectors(atmosConfig schema.AtmosConfiguration) {
func RegisterCustomDetectors(atmosConfig schema.AtmosConfiguration, source string) {
getter.Detectors = append(
[]getter.Detector{
&CustomGitHubDetector{AtmosConfig: atmosConfig},
&CustomGitHubDetector{AtmosConfig: atmosConfig, source: source},
},
getter.Detectors...,
)
Expand All @@ -159,24 +168,68 @@ func GoGetterGet(
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

// Register custom detectors
RegisterCustomDetectors(atmosConfig)
// Register custom detectors, passing the original `src` to the CustomGitHubDetector.
// go-getter typically strips subdirectories before calling the detector, so the
// unaltered source is needed to identify whether a top-level repository or a
// subdirectory was specified (e.g., for appending "//." only when no subdir is present).
RegisterCustomDetectors(atmosConfig, src)

client := &getter.Client{
Ctx: ctx,
Src: src,
// Destination where the files will be stored. This will create the directory if it doesn't exist
Dst: dest,
Mode: clientMode,
}
Getters: map[string]getter.Getter{
// Overriding 'git'
"git": &CustomGitGetter{},
"file": &getter.FileGetter{},
"hg": &getter.HgGetter{},
"http": &getter.HttpGetter{},
"https": &getter.HttpGetter{},
// "s3": &getter.S3Getter{}, // add as needed
// "gcs": &getter.GCSGetter{},

},
}
if err := client.Get(); err != nil {
return err
}

return nil
}

// CustomGitGetter is a custom getter for git (git::) that removes symlinks
type CustomGitGetter struct {
getter.GitGetter
}

// Implements the custom getter logic removing symlinks
func (c *CustomGitGetter) Get(dst string, url *url.URL) error {
// Normal clone
if err := c.GitGetter.Get(dst, url); err != nil {
return err
}
// Remove symlinks
return removeSymlinks(dst)
}

// removeSymlinks walks the destination directory and removes any symlinks
// it encounters.
func removeSymlinks(root string) error {
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.Mode()&os.ModeSymlink != 0 {
u.LogWarning(schema.AtmosConfiguration{}, fmt.Sprintf("Removing symlink: %s", path))
// It's a symlink, remove it
return os.Remove(path)
}
return nil
})
}

// DownloadDetectFormatAndParseFile downloads a remote file, detects the format of the file (JSON, YAML, HCL) and parses the file into a Go type
func DownloadDetectFormatAndParseFile(atmosConfig schema.AtmosConfiguration, file string) (any, error) {
tempDir := os.TempDir()
Expand Down
2 changes: 1 addition & 1 deletion internal/exec/vendor_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ func downloadAndInstall(p *pkgAtmosVendor, dryRun bool, atmosConfig schema.Atmos
}

}
if err := copyToTarget(atmosConfig, tempDir, p.targetPath, &p.atmosVendorSource, p.sourceIsLocalFile, p.uri); err != nil {
if err := copyToTargetWithPatterns(atmosConfig, tempDir, p.targetPath, &p.atmosVendorSource, p.sourceIsLocalFile, p.uri); err != nil {
return installedPkgMsg{
err: fmt.Errorf("failed to copy package: %w", err),
name: p.name,
Expand Down
11 changes: 11 additions & 0 deletions tests/fixtures/scenarios/vendor/vendor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,14 @@ spec:
- "**/*.tftmpl"
- "**/modules/**"
excluded_paths: []

- component: "test globs"
source: "github.com/cloudposse/atmos.git"
included_paths:
- "**/{demo-library,demo-stacks}/**/*.{tf,md}"
excluded_paths:
- "**/demo-library/**/*.{tfvars,tf}"
targets:
- "components/library/"
tags:
- demo
Loading