stages/copy: add option to remove destination before copying

Extend the copy stage to optionally allow removing the destination
before copying. This allows one to not follow symlinks if the
destination is a symlink to a file. By default, `cp` would change
the file pointed to by the destination if it is symlink.

Extend the stage doc text to cover the behavior with regard to
destination being a symlink.

Add unit tests for the copy stage to also test the newly added option.

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
This commit is contained in:
Tomáš Hozza 2023-02-15 15:55:32 +01:00 committed by Achilleas Koutsou
parent d3229dc929
commit 200c2b0129
6 changed files with 1701 additions and 3 deletions

View file

@ -9,6 +9,14 @@ destination is an url. Supported locations ('schemes') are `tree`,
The path format follows the rsync convention that if the paths
ends with a slash `/` the content of that directory is copied not
the directory itself.
Note that the stage by default does not remove the destination
before copying. As a result, if the destination is an existing
symlink to a file, then this file will be overwritten, instead of
the symlink being replaced. If you want to replace the symlink
with a file, you need to set the `remove_destination` option to
`true`. This option works only for files, not directories or
symlinks to directories.
"""
import os
@ -54,6 +62,11 @@ SCHEMA_2 = r"""
"pattern": "^tree:\/\/\/"
}
]
},
"remove_destination": {
"type": "boolean",
"description": "Remove the destination before copying. Works only for files, not directories.",
"default": false
}
}
}
@ -126,12 +139,14 @@ def main(args, options):
for path in items:
src = parse_location(path["from"], args)
dst = parse_location(path["to"], args)
remove_destination = path.get("remove_destination", False)
print(f"copying '{src}' -> '{dst}'")
subprocess.run(["cp", "-a", "--reflink=auto",
src, dst],
check=True)
cmd = ["cp", "-a", "--reflink=auto"]
if remove_destination:
cmd.append("--remove-destination")
subprocess.run(cmd + [src, dst], check=True)
return 0