in internal/pkg/agent/install/install.go [237:356]
func copyFiles(copyConcurrency int, pathMappings []map[string]string, srcDir string, topPath string, skipFn func(string) bool) error {
// copy source into install path
// these are needed to keep track of what we already copied
copiedFiles := map[string]struct{}{}
// collect any symlink we found that need remapping
symlinks := map[string]string{}
var copyErrors []error
// Start copying the remapped paths first
for _, pathMapping := range pathMappings {
for packagePath, installedPath := range pathMapping {
// flag the original path as handled
copiedFiles[packagePath] = struct{}{}
srcPath := filepath.Join(srcDir, packagePath)
dstPath := filepath.Join(topPath, installedPath)
err := copy.Copy(srcPath, dstPath, copy.Options{
OnSymlink: func(_ string) copy.SymlinkAction {
return copy.Shallow
},
Skip: func(srcinfo os.FileInfo, src, dest string) (bool, error) {
relPath, err := filepath.Rel(srcDir, src)
if err != nil {
return false, fmt.Errorf("calculating relative path for %s: %w", src, err)
}
if skipFn != nil && skipFn(relPath) {
return true, nil
}
return false, nil
},
Sync: true,
NumOfWorkers: int64(copyConcurrency),
})
if err != nil {
return errors.New(
err,
fmt.Sprintf("failed to copy source directory (%s) to destination (%s)", packagePath, installedPath),
errors.M("source", packagePath), errors.M("destination", installedPath),
)
}
}
}
// copy the remaining files excluding overlaps with the mapped paths
err := copy.Copy(srcDir, topPath, copy.Options{
OnSymlink: func(source string) copy.SymlinkAction {
target, err := os.Readlink(source)
if err != nil {
// error reading the link, not much choice to leave it unchanged and collect the error
copyErrors = append(copyErrors, fmt.Errorf("unable to read link %q for remapping", source))
return copy.Skip
}
// if we find a link, check if its target need to be remapped, in which case skip it for now and save it for
// later creation with the remapped target
for _, pathMapping := range pathMappings {
for srcPath, dstPath := range pathMapping {
srcPathLocal := filepath.FromSlash(srcPath)
dstPathLocal := filepath.FromSlash(dstPath)
if strings.HasPrefix(target, srcPathLocal) {
newTarget := strings.Replace(target, srcPathLocal, dstPathLocal, 1)
rel, err := filepath.Rel(srcDir, source)
if err != nil {
copyErrors = append(copyErrors, fmt.Errorf("extracting relative path for %q using %q as base: %w", source, srcDir, err))
return copy.Skip
}
symlinks[rel] = newTarget
return copy.Skip
}
}
}
return copy.Shallow
},
Skip: func(srcinfo os.FileInfo, src, dest string) (bool, error) {
relPath, err := filepath.Rel(srcDir, src)
if err != nil {
return false, fmt.Errorf("calculating relative path for %s: %w", src, err)
}
if skipFn != nil && skipFn(relPath) {
return true, nil
}
// check if we already handled this path as part of the mappings: if we did, skip it
relPath = filepath.ToSlash(relPath)
_, ok := copiedFiles[relPath]
return ok, nil
},
Sync: true,
NumOfWorkers: int64(copyConcurrency),
})
if err != nil {
return errors.New(
err,
fmt.Sprintf("failed to copy source directory (%s) to destination (%s)", srcDir, topPath),
errors.M("source", srcDir), errors.M("destination", topPath),
)
}
if len(copyErrors) > 0 {
return fmt.Errorf("errors encountered during copy from %q to %q: %w", srcDir, topPath, goerrors.Join(copyErrors...))
}
// Create the remapped symlinks
for src, target := range symlinks {
absSrcPath := filepath.Join(topPath, src)
err := os.Symlink(target, absSrcPath)
if err != nil {
return errors.New(
err,
fmt.Sprintf("failed to link source %q to destination %q", absSrcPath, target),
)
}
}
return nil
}