Skip to content

Commit 7643ae9

Browse files
committed
Add force & force_backup args to files.[file|directory|link] operations. (#631)
* Add `force` & `force_backup` args to `files.[file|directory|link]` operations. This makes it possible to have pyinfra move or remove an existing and invalid path before creating the desired one. * Add `force_backup_dir` argument to `files.[file|directory|link]` operations. * Fix rebase error in config defaults.
1 parent f220abb commit 7643ae9

File tree

11 files changed

+185
-3
lines changed

11 files changed

+185
-3
lines changed

pyinfra/api/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
'DOAS': False,
4545
'DOAS_USER': None,
4646

47+
# Use doas and optional user
48+
'DOAS': False,
49+
'DOAS_USER': None,
50+
4751
# Only show errors, but don't count as failure
4852
'IGNORE_ERRORS': False,
4953

pyinfra/operations/files.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,19 @@ def _validate_path(path):
946946
raise OperationTypeError('`path` must be a string or `os.PathLike` object')
947947

948948

949+
def _raise_or_remove_invalid_path(fs_type, path, force, force_backup, force_backup_dir):
950+
if force:
951+
if force_backup:
952+
backup_path = '{0}.{1}'.format(path, get_timestamp())
953+
if force_backup_dir:
954+
backup_path = '{0}/{1}'.format(force_backup_dir, backup_path)
955+
yield 'mv {0} {1}'.format(path, backup_path)
956+
else:
957+
yield 'rm -rf {0}'.format(path)
958+
else:
959+
raise OperationError('{0} exists and is not a {1}'.format(path, fs_type))
960+
961+
949962
@operation(pipeline_facts={
950963
'link': 'path',
951964
})
@@ -954,6 +967,7 @@ def link(
954967
target=None, present=True, assume_present=False,
955968
user=None, group=None, symbolic=True,
956969
create_remote_dir=True,
970+
force=False, force_backup=True, force_backup_dir=None,
957971
state=None, host=None,
958972
):
959973
'''
@@ -967,6 +981,9 @@ def link(
967981
+ group: group to own the link
968982
+ symbolic: whether to make a symbolic link (vs hard link)
969983
+ create_remote_dir: create the remote directory if it doesn't exist
984+
+ force: if the target exists and is not a file, move or remove it and continue
985+
+ force_backup: set to ``False`` to remove any existing non-file when ``force=True``
986+
+ force_backup_dir: directory to move any backup to when ``force=True``
970987
971988
``create_remote_dir``:
972989
If the remote directory does not exist it will be created using the same
@@ -1014,7 +1031,10 @@ def link(
10141031

10151032
# Not a link?
10161033
if info is False:
1017-
raise OperationError('{0} exists and is not a link'.format(path))
1034+
yield _raise_or_remove_invalid_path(
1035+
'link', path, force, force_backup, force_backup_dir,
1036+
)
1037+
info = None
10181038

10191039
add_args = ['ln']
10201040
if symbolic:
@@ -1097,6 +1117,7 @@ def file(
10971117
present=True, assume_present=False,
10981118
user=None, group=None, mode=None, touch=False,
10991119
create_remote_dir=True,
1120+
force=False, force_backup=True, force_backup_dir=None,
11001121
state=None, host=None,
11011122
):
11021123
'''
@@ -1110,6 +1131,9 @@ def file(
11101131
+ mode: permissions of the files as an integer, eg: 755
11111132
+ touch: whether to touch the file
11121133
+ create_remote_dir: create the remote directory if it doesn't exist
1134+
+ force: if the target exists and is not a file, move or remove it and continue
1135+
+ force_backup: set to ``False`` to remove any existing non-file when ``force=True``
1136+
+ force_backup_dir: directory to move any backup to when ``force=True``
11131137
11141138
``create_remote_dir``:
11151139
If the remote directory does not exist it will be created using the same
@@ -1139,7 +1163,10 @@ def file(
11391163

11401164
# Not a file?!
11411165
if info is False:
1142-
raise OperationError('{0} exists and is not a file'.format(path))
1166+
yield _raise_or_remove_invalid_path(
1167+
'file', path, force, force_backup, force_backup_dir,
1168+
)
1169+
info = None
11431170

11441171
# Doesn't exist & we want it
11451172
if not assume_present and info is None and present:
@@ -1206,6 +1233,7 @@ def directory(
12061233
path,
12071234
present=True, assume_present=False,
12081235
user=None, group=None, mode=None, recursive=False,
1236+
force=False, force_backup=True, force_backup_dir=None,
12091237
_no_check_owner_mode=False,
12101238
_no_fail_on_link=False,
12111239
state=None, host=None,
@@ -1220,6 +1248,9 @@ def directory(
12201248
+ group: group to own the folder
12211249
+ mode: permissions of the folder
12221250
+ recursive: recursively apply user/group/mode
1251+
+ force: if the target exists and is not a file, move or remove it and continue
1252+
+ force_backup: set to ``False`` to remove any existing non-file when ``force=True``
1253+
+ force_backup_dir: directory to move any backup to when ``force=True``
12231254
12241255
Examples:
12251256
@@ -1257,7 +1288,10 @@ def directory(
12571288
if _no_fail_on_link and host.get_fact(Link, path=path):
12581289
host.noop('directory {0} already exists (as a link)'.format(path))
12591290
return
1260-
raise OperationError('{0} exists and is not a directory'.format(path))
1291+
yield _raise_or_remove_invalid_path(
1292+
'directory', path, force, force_backup, force_backup_dir,
1293+
)
1294+
info = None
12611295

12621296
# Doesn't exist & we want it
12631297
if not assume_present and info is None and present:
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"args": ["testdir"],
3+
"kwargs": {
4+
"force": true
5+
},
6+
"facts": {
7+
"files.Directory": {
8+
"path=testdir": false
9+
}
10+
},
11+
"commands": [
12+
"mv testdir testdir.a-timestamp",
13+
"mkdir -p testdir"
14+
]
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"args": ["testdir"],
3+
"kwargs": {
4+
"force": true,
5+
"force_backup_dir": "/tmp"
6+
},
7+
"facts": {
8+
"files.Directory": {
9+
"path=testdir": false
10+
}
11+
},
12+
"commands": [
13+
"mv testdir /tmp/testdir.a-timestamp",
14+
"mkdir -p testdir"
15+
]
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"args": ["testdir"],
3+
"kwargs": {
4+
"force": true,
5+
"force_backup": false
6+
},
7+
"facts": {
8+
"files.Directory": {
9+
"path=testdir": false
10+
}
11+
},
12+
"commands": [
13+
"rm -rf testdir",
14+
"mkdir -p testdir"
15+
]
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"args": ["testfile"],
3+
"kwargs": {
4+
"force": true
5+
},
6+
"facts": {
7+
"files.File": {
8+
"path=testfile": false
9+
}
10+
},
11+
"commands": [
12+
"mv testfile testfile.a-timestamp",
13+
"touch testfile"
14+
]
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"args": ["testfile"],
3+
"kwargs": {
4+
"force": true,
5+
"force_backup_dir": "/tmp/somewhere"
6+
},
7+
"facts": {
8+
"files.File": {
9+
"path=testfile": false
10+
}
11+
},
12+
"commands": [
13+
"mv testfile /tmp/somewhere/testfile.a-timestamp",
14+
"touch testfile"
15+
]
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"args": ["testfile"],
3+
"kwargs": {
4+
"force": true,
5+
"force_backup": false
6+
},
7+
"facts": {
8+
"files.File": {
9+
"path=testfile": false
10+
}
11+
},
12+
"commands": [
13+
"rm -rf testfile",
14+
"touch testfile"
15+
]
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"args": ["testlink"],
3+
"kwargs": {
4+
"target": "/etc/init.d/nginx",
5+
"force": true
6+
},
7+
"facts": {
8+
"files.Link": {
9+
"path=testlink": false
10+
}
11+
},
12+
"commands": [
13+
"mv testlink testlink.a-timestamp",
14+
"ln -s /etc/init.d/nginx testlink"
15+
]
16+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"args": ["testlink"],
3+
"kwargs": {
4+
"target": "/etc/init.d/nginx",
5+
"force": true,
6+
"force_backup_dir": "/tmp/somewhere"
7+
},
8+
"facts": {
9+
"files.Link": {
10+
"path=testlink": false
11+
}
12+
},
13+
"commands": [
14+
"mv testlink /tmp/somewhere/testlink.a-timestamp",
15+
"ln -s /etc/init.d/nginx testlink"
16+
]
17+
}

0 commit comments

Comments
 (0)