Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 42 additions & 0 deletions src/pyinfra/operations/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,15 @@ def _file_equal(local_path: str | IO[Any] | None, remote_path: str) -> bool:
return False


def _remote_file_equal(remote_path_a: str, remote_path_b: str) -> bool:
for fact in [Sha1File, Md5File, Sha256File]:
sum_a = host.get_fact(fact, path=remote_path_a)
sum_b = host.get_fact(fact, path=remote_path_b)
if sum_a and sum_b:
return sum_a == sum_b
return False


@operation(
# We don't (currently) cache the local state, so there's nothing we can
# update to flag the local file as present.
Expand Down Expand Up @@ -1312,6 +1321,39 @@ def move(src: str, dest: str, overwrite=False):
yield StringCommand("mv", QuoteString(src), QuoteString(dest))


@operation()
def copy(src: str, dest: str, overwrite=False):
"""
Copy remote file/directory/link into remote directory

+ src: remote file/directory to copy
+ dest: remote directory to copy `src` into
+ overwrite: whether to overwrite dest, if present
"""
src_is_dir = host.get_fact(Directory, src)
if not host.get_fact(File, src) and not src_is_dir:
raise OperationError(f"src {src} does not exist")

if not host.get_fact(Directory, dest):
raise OperationError(f"dest {dest} is not an existing directory")

dest_file_path = os.path.join(dest, os.path.basename(src))
dest_file_exists = host.get_fact(File, dest_file_path)
if dest_file_exists and not overwrite:
if _remote_file_equal(src, dest_file_path):
host.noop(f"{dest_file_path} already exists")
return
else:
raise OperationError(f"{dest_file_path} already exists and is different than src")

cp_cmd = ["cp -r"]

if overwrite:
cp_cmd.append("-f")

yield StringCommand(*cp_cmd, QuoteString(src), QuoteString(dest))


def _validate_path(path):
try:
return os.fspath(path)
Expand Down
22 changes: 22 additions & 0 deletions tests/operations/files.copy/copies_directory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"require_platform": ["Darwin", "Linux"],
"kwargs": {
"src": "/tmp/src_dir",
"dest": "/tmp/dest_dir",
"overwrite": false
},
"facts": {
"files.File": {
"path=/tmp/src_dir/file": true,

"path=/tmp/src_dir": null,
"path=/tmp/dest_dir/src_dir": null,
"path=/tmp/dest_dir/src_dir/file": null
},
"files.Directory": {
"path=/tmp/src_dir": true,
"path=/tmp/dest_dir": true
}
},
"commands": ["cp -r /tmp/src_dir /tmp/dest_dir"]
}
21 changes: 21 additions & 0 deletions tests/operations/files.copy/copies_directory_overwriting.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"require_platform": ["Darwin", "Linux"],
"kwargs": {
"src": "/tmp/src_dir",
"dest": "/tmp/dest_dir",
"overwrite": true
},
"facts": {
"files.File": {
"path=/tmp/src_dir/file": true,
"path=/tmp/src_dir": null,
"path=/tmp/dest_dir/src_dir": null,
"path=/tmp/dest_dir/src_dir/file": null
},
"files.Directory": {
"path=/tmp/src_dir": true,
"path=/tmp/dest_dir": true
}
},
"commands": ["cp -r -f /tmp/src_dir /tmp/dest_dir"]
}
19 changes: 19 additions & 0 deletions tests/operations/files.copy/copies_file.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"require_platform": ["Darwin", "Linux"],
"kwargs": {
"src": "/tmp/src_dir/file",
"dest": "/tmp/dest_dir",
"overwrite": false
},
"facts": {
"files.File": {
"path=/tmp/src_dir/file": true,
"path=/tmp/dest_dir/file": null
},
"files.Directory": {
"path=/tmp/dest_dir": true,
"path=/tmp/src_dir/file": null
}
},
"commands": ["cp -r /tmp/src_dir/file /tmp/dest_dir"]
}
19 changes: 19 additions & 0 deletions tests/operations/files.copy/copies_file_overwriting.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"require_platform": ["Darwin", "Linux"],
"kwargs": {
"src": "/tmp/src_dir/file",
"dest": "/tmp/dest_dir",
"overwrite": true
},
"facts": {
"files.File": {
"path=/tmp/src_dir/file": true,
"path=/tmp/dest_dir/file": true
},
"files.Directory": {
"path=/tmp/dest_dir": true,
"path=/tmp/src_dir/file": null
}
},
"commands": ["cp -r -f /tmp/src_dir/file /tmp/dest_dir"]
}
21 changes: 21 additions & 0 deletions tests/operations/files.copy/invalid_dest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"kwargs": {
"src": "/tmp/src_dir/file",
"dest": "/tmp/dest_dir",
"overwrite": false
},
"facts": {
"files.File": {
"path=/tmp/src_dir/file": true,
"path=/tmp/dest_dir/file": null
},
"files.Directory": {
"path=/tmp/dest_dir": null,
"path=/tmp/src_dir/file": null
}
},
"exception": {
"name": "OperationError",
"message": "dest /tmp/dest_dir is not an existing directory"
}
}
26 changes: 26 additions & 0 deletions tests/operations/files.copy/invalid_overwrite.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"require_platform": ["Darwin", "Linux"],
"kwargs": {
"src": "/tmp/src_dir/file",
"dest": "/tmp/dest_dir",
"overwrite": false
},
"facts": {
"files.File": {
"path=/tmp/src_dir/file": true,
"path=/tmp/dest_dir/file": true
},
"files.Directory": {
"path=/tmp/dest_dir": true,
"path=/tmp/src_dir/file": null
},
"files.Sha1File": {
"path=/tmp/src_dir/file": "abc",
"path=/tmp/dest_dir/file": "xyz"
}
},
"exception": {
"name": "OperationError",
"message": "/tmp/dest_dir/file already exists and is different than src"
}
}
21 changes: 21 additions & 0 deletions tests/operations/files.copy/invalid_src.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"kwargs": {
"src": "/tmp/src_dir/file",
"dest": "/tmp/dest_dir",
"overwrite": false
},
"facts": {
"files.File": {
"path=/tmp/src_dir/file": null,
"path=/tmp/dest_dir/file": null
},
"files.Directory": {
"path=/tmp/dest_dir": true,
"path=/tmp/src_dir/file": null
}
},
"exception": {
"name": "OperationError",
"message": "src /tmp/src_dir/file does not exist"
}
}
24 changes: 24 additions & 0 deletions tests/operations/files.copy/noop_dest_file_exists.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"require_platform": ["Darwin", "Linux"],
"kwargs": {
"src": "/tmp/src_dir/file",
"dest": "/tmp/dest_dir",
"overwrite": false
},
"facts": {
"files.File": {
"path=/tmp/src_dir/file": true,
"path=/tmp/dest_dir/file": true
},
"files.Directory": {
"path=/tmp/dest_dir": true,
"path=/tmp/src_dir/file": null
},
"files.Sha1File": {
"path=/tmp/src_dir/file": "abc",
"path=/tmp/dest_dir/file": "abc"
}
},
"commands": [],
"noop_description": "/tmp/dest_dir/file already exists"
}