func copyFiles()

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
}