pyinfra-dev / pyinfra

pyinfra turns Python code into shell commands and runs them on your servers. Execute ad-hoc commands and write declarative operations. Target SSH servers, local machine and Docker containers. Fast and scales from one server to thousands.
https://pyinfra.com
MIT License
3.91k stars 382 forks source link

hard to remove file-or-link #807

Open drewp opened 2 years ago

drewp commented 2 years ago
1  files.file(path='/etc/resolv.conf', present=False)
2  files.link(path='/etc/resolv.conf', present=False)
3  files.template(src='templates/resolv.conf.j2', dest='/etc/resolv.conf', ns='10.2.0.1')

If I run 1,2,3 I get --> pyinfra error: /etc/resolv.conf exists and is not a file

If I run just 2,3 I get (on another host) --> pyinfra error: /etc/resolv.conf exists and is not a link

If I run just 3 I get no errors, but I'm left with some hosts having a symlink and some having a file.

I expected just 3 to work and replace symlinks with the templated file.

May be related to #791

Meta

System: Linux
  Platform: Linux-5.13.0-40-generic-x86_64-with-glibc2.34
  Release: 5.13.0-40-generic
  Machine: x86_64
pyinfra: v2.1
Executable: env/bin/pyinfra
Python: 3.9.9 (CPython, GCC 10.3.0)
drewp commented 2 years ago

I thought this would be a workaround: server.shell(["[[ -L /etc/resolv.conf ]] && rm /etc/resolv.conf"])

--> Starting operation: Server/Shell (['[[ -L /etc/passwd ]] && rm /etc/resolv.conf'])
    [dash] sh: 1: [[: not found
    [dash] Error
sysadmin75 commented 2 years ago

I think you want the force option. You should be able to only have one operation when using force. So you should be able to get away with doing:

from pyinfra.operations import files
files.file(path='/tmp/resolv.conf', present=False, force=True)

And this should work for both symlink and regular files.

The downside of doing this is the file is renamed, instead of fully being deleted.

Fizzadar commented 2 years ago

I think this is correct - by default the files.[link|directory|file] operations will raise these errors if the path exists and doesn't match the desired type. The force=True argument can be used to rename before creating the expected path. There is also a force_backup (default True) that can be set to False to remove the path without backup.

The files.template operation does not check anything about the target path which is why it'll work whether the path is a link or a file. I think this makes sense so a template could be written to a link directly?

So I'm not sure there's a bug to fix except maybe the error messages need improvement, something like:

--> pyinfra error: path /etc/resolv.conf already exists as a , use force=True to move the and create the (<include call location here)