Skip to content

Commit 025e1db

Browse files
authored
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 579f3f6 commit 025e1db

11 files changed

+185
-3
lines changed

pyinfra/api/config.py

+4
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

+37-3
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,19 @@ def _validate_path(path):
935935
raise OperationTypeError('`path` must be a string or `os.PathLike` object')
936936

937937

938+
def _raise_or_remove_invalid_path(fs_type, path, force, force_backup, force_backup_dir):
939+
if force:
940+
if force_backup:
941+
backup_path = '{0}.{1}'.format(path, get_timestamp())
942+
if force_backup_dir:
943+
backup_path = '{0}/{1}'.format(force_backup_dir, backup_path)
944+
yield 'mv {0} {1}'.format(path, backup_path)
945+
else:
946+
yield 'rm -rf {0}'.format(path)
947+
else:
948+
raise OperationError('{0} exists and is not a {1}'.format(path, fs_type))
949+
950+
938951
@operation(pipeline_facts={
939952
'link': 'path',
940953
})
@@ -943,6 +956,7 @@ def link(
943956
target=None, present=True, assume_present=False,
944957
user=None, group=None, symbolic=True,
945958
create_remote_dir=True,
959+
force=False, force_backup=True, force_backup_dir=None,
946960
state=None, host=None,
947961
):
948962
'''
@@ -956,6 +970,9 @@ def link(
956970
+ group: group to own the link
957971
+ symbolic: whether to make a symbolic link (vs hard link)
958972
+ create_remote_dir: create the remote directory if it doesn't exist
973+
+ force: if the target exists and is not a file, move or remove it and continue
974+
+ force_backup: set to ``False`` to remove any existing non-file when ``force=True``
975+
+ force_backup_dir: directory to move any backup to when ``force=True``
959976
960977
``create_remote_dir``:
961978
If the remote directory does not exist it will be created using the same
@@ -1003,7 +1020,10 @@ def link(
10031020

10041021
# Not a link?
10051022
if info is False:
1006-
raise OperationError('{0} exists and is not a link'.format(path))
1023+
yield _raise_or_remove_invalid_path(
1024+
'link', path, force, force_backup, force_backup_dir,
1025+
)
1026+
info = None
10071027

10081028
add_args = ['ln']
10091029
if symbolic:
@@ -1086,6 +1106,7 @@ def file(
10861106
present=True, assume_present=False,
10871107
user=None, group=None, mode=None, touch=False,
10881108
create_remote_dir=True,
1109+
force=False, force_backup=True, force_backup_dir=None,
10891110
state=None, host=None,
10901111
):
10911112
'''
@@ -1099,6 +1120,9 @@ def file(
10991120
+ mode: permissions of the files as an integer, eg: 755
11001121
+ touch: whether to touch the file
11011122
+ create_remote_dir: create the remote directory if it doesn't exist
1123+
+ force: if the target exists and is not a file, move or remove it and continue
1124+
+ force_backup: set to ``False`` to remove any existing non-file when ``force=True``
1125+
+ force_backup_dir: directory to move any backup to when ``force=True``
11021126
11031127
``create_remote_dir``:
11041128
If the remote directory does not exist it will be created using the same
@@ -1128,7 +1152,10 @@ def file(
11281152

11291153
# Not a file?!
11301154
if info is False:
1131-
raise OperationError('{0} exists and is not a file'.format(path))
1155+
yield _raise_or_remove_invalid_path(
1156+
'file', path, force, force_backup, force_backup_dir,
1157+
)
1158+
info = None
11321159

11331160
# Doesn't exist & we want it
11341161
if not assume_present and info is None and present:
@@ -1195,6 +1222,7 @@ def directory(
11951222
path,
11961223
present=True, assume_present=False,
11971224
user=None, group=None, mode=None, recursive=False,
1225+
force=False, force_backup=True, force_backup_dir=None,
11981226
_no_check_owner_mode=False,
11991227
_no_fail_on_link=False,
12001228
state=None, host=None,
@@ -1209,6 +1237,9 @@ def directory(
12091237
+ group: group to own the folder
12101238
+ mode: permissions of the folder
12111239
+ recursive: recursively apply user/group/mode
1240+
+ force: if the target exists and is not a file, move or remove it and continue
1241+
+ force_backup: set to ``False`` to remove any existing non-file when ``force=True``
1242+
+ force_backup_dir: directory to move any backup to when ``force=True``
12121243
12131244
Examples:
12141245
@@ -1246,7 +1277,10 @@ def directory(
12461277
if _no_fail_on_link and host.get_fact(Link, path=path):
12471278
host.noop('directory {0} already exists (as a link)'.format(path))
12481279
return
1249-
raise OperationError('{0} exists and is not a directory'.format(path))
1280+
yield _raise_or_remove_invalid_path(
1281+
'directory', path, force, force_backup, force_backup_dir,
1282+
)
1283+
info = None
12501284

12511285
# Doesn't exist & we want it
12521286
if not assume_present and info is None and present:
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+
}
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+
}
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+
}
+15
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+
}
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+
}
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+
}
+16
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+
}
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+
}
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": false
7+
},
8+
"facts": {
9+
"files.Link": {
10+
"path=testlink": false
11+
}
12+
},
13+
"commands": [
14+
"rm -rf testlink",
15+
"ln -s /etc/init.d/nginx testlink"
16+
]
17+
}

0 commit comments

Comments
 (0)