I have a simple Python 3 script (I’m running Python 3.6.1, compiled from source) that does the following 3 things:
- Connects to remote server(s) and using
scp
to get files - Processes the files.
- Connects to another remote server and uploads the processed files.
I moved this script from a soon-to-be-retired CentOS 6 host to a CentOS 7 host, and when I ran it there I received the following error message:
AttributeError: module 'paramiko' has no attribute 'SSHClient'
The line number specified in the stack trace led me to:
ssh = paramiko.SSHClient()
First things first: Google the error. Someone else has seen this before. Sure enough, on StackOverflow I found Paramiko: Module object has no attribute error ‘SSHClient’
But no, that’s not the problem I’m having.
I tried to create the the simplest possible script that would reproduce the error:
#!/usr/bin/python3
import paramiko
def main():
ssh = paramiko.SSHClient()
if __name__ == "__main__":
main()
I ran it as a super-user and received no errors:
$ sudo /usr/bin/python3 test.py
I ran it as myself, though, and it reproduced the error message. Maybe something with the user permissions?
$ ls -l /usr/local/lib/python3.6/site-packages/
total 1168
...
drwxr-x---. 3 root root 4096 May 29 14:25 paramiko
...
Oh! From that you can see that unless you are the root user, or a member of the root group, there’s no way you can even see the files within the paramiko directory.
What’s the default umask on the system?
$ umask
0027
That explains it. Now, to fix it. I could probably just run:
$ sudo chmod -R 0755 /usr/local/lib/python3.6/site-packages/*
That should have been the end of that, problem solved! But in my case, installation of the pip modules had been handled by Ansible. I needed to fix the Ansible tasks to account for restrictive umask settings on future deployments. See the umask
parameter in the documentation for the Ansible pip module. I updated the task:
- name: Install specified python requirements
pip:
executable: /usr/local/bin/pip3
requirements: /tmp/pip3-packages
umask: 0022
Running the playbook with that task, I received an error:
fatal: [trinculo.osric.net]: FAILED! => {"changed": false, "details": "invalid literal for int() with base 8: '18'", "msg": "umask must be an octal integer"}
Another helpful StackOverflow post suggested the value needed to be in quotes:
- name: Install specified python requirements
pip:
executable: /usr/local/bin/pip3
requirements: /tmp/pip3-packages
umask: "0022"
Now the playbook runs without error, but it doesn’t change the existing permissions. The task does nothing, since the Python pip modules are already installed. To really test the playbook, I need to clear out the existing modules first.
Warning: this breaks things!:
$ sudo rm -rf /usr/local/lib/python3.6/site-packages/*
I tried running the playbook again and received this error message:
stderr: Traceback (most recent call last):\n File "/usr/local/bin/pip3", line 7, in <module>\n from pip import main\nModuleNotFoundError: No module named 'pip'\n
I wasn’t able to run pip
at all:
$ pip3
Traceback (most recent call last):
File "/usr/local/bin/pip3", line 7, in <module>
from pip import main
ModuleNotFoundError: No module named 'pip'
Clearly, I had deleted something important! I reinstalled Python from the gzipped source tarball for Python 3.6.1 (newer versions available from Python source releases) and then everything worked as expected.