I have this python fabric fabfile. I want to run the sudo command without prompting for a password. Would like to save the password in the file. Does Fabric3 no longer support the watchers option ? Is there any way I can put the password in the script?
from fabric.api import *
from invoke import Responder
env.user = "usera"
env.password = "password"
env.sudo_user = "usera"
env.password = "password"
env.sudo_prompt = "Password:"
sudopass = Responder (
pattern=r'Password:',
response=env.password + '\n'
)
def itm_run ():
# result = run("sudo systemctl restart S99itm", pty=True, watchers=[sudopass])
result = run("sudo systemctl restart S99itm")
print(result)
The watchers still work, otherwise check your pattern. You can also add echo=True.
For the password it is best to keep it in an env variable or such. so you can do env.password = getenv('SOMEHOST_SOMEUSER_PASSWORD')
I'm using fabric and I want to download a file simultaneously on different hosts at the same time but when I use
env.hosts = ['192.168.1.2', '192.168.1.3', '192.168.1.4']
I always get No hosts found. Please specify (single) host string for connection:
from fabric.api import env , run, sudo, settings
env.user = 'root' #all the servers have the same username
env.hosts = ['192.168.1.2', '192.168.1.3', '192.168.1.4']
env.key_filename = "~/.ssh/id_rsa" # I have their ssh key
run('wget file') #The command I need to run in parrallel
I want to run this from a python code without using the fab command.
I usually use the #parallel decorator (http://docs.fabfile.org/en/1.13/usage/parallel.html) and do something like this.
env.use_ssh_config = True
env.user = 'ubuntu'
env.sudo_user = 'ubuntu'
env.roledefs = {
'uat': ['website_uat'],
'prod': ['website01', 'website02']
}
#task
def deploy(role_from_arg, **kwargs):
# on each remote download file
execute(download_file, role=role_from_arg, **kwargs)
#parallel
def download_file(*args, **kwargs):
# some code to download file here
Then i can run fab deploy:prod
I don't uderstand how to define multiple hosts in python fabric. I have a hosts.txt file contains a connection data in this form:
user#hostname:22 password
user#otherhost:22 password
user#thirdhost:22 password
and sample code:
#!/usr/bin/python
import pprint
from fabric.api import *
for line in open('hosts.txt','r').readlines():
host, passw = line.split()
env.hosts.append(host)
env.passwords[host] = passw
pprint.pprint( env.hosts )
def test():
sudo("mkdir /a")
test();
Unfortunately I'm getting message
No hosts found. Please specify (single) host string for connection
pprint shows me a proper hosts list. What should I do to perform my tasks to all hosts inside hosts.txt file?
I doing it wrong way. Instead of calling a function from a python file, i should run it from command line this way:
fab -f my_file_with_functions function_name
so this solves the problem
This question is about best practices. I'm running a deployment script with Fabric. My deployment user 'deploy' needs sudo to restart services. So I am using the sudo function from fabric to run these commands in my script. This works fine but prompts for password during script execution. I DON'T want to type a password during deployments. What's the best practice here. The only solution I can think of is changing the sudo permissions to not require password for the commands my deployment user runs. This doesn't seem right to me.
The ideal solution is to create a user on your server that is used only for deployment (eg, deploy). Then, set env.user=deploy in your fabfile. Then on your servers, you can give the user the necessary permission on a command-by-command basis in a sudoers file:
IMPORTANT: Always use sudo visudo to modify a sudoers file
Cmnd_Alias RELOAD_SITE = /bin/bash -l -c supervisorctl*, /usr/bin/supervisorctl*
deploy ALL = NOPASSWD: RELOAD_SITE
You can add as many Cmnd_Alias directives as is needed by the deploy user, then grant NOPASSWD access for each of those commands. See man sudoers for more details.
I like to keep my deploy-specific sudoers config in /etc/sudoers.d/deploy and include that file from /etc/sudoers by adding: includedir /etc/suoders.d at the end.
You can use:
fabric.api import env
# [...]
env.password = 'yourpassword'
The best way to do this is with subtasks. You can prompt for a password in the fabfile and never expose any passwords, nor make reckless configuration changes to sudo on the remote system(s).
import getpass
from fabric.api import env, parallel, run, task
from fabric.decorators import roles
from fabric.tasks import execute
env.roledefs = {'my_role': ['host1', 'host2']}
#task
# #parallel -- uncomment if you need parallel execution, it'll work!
#roles('my_role')
def deploy(*args, **kwargs):
print 'deploy args:', args, kwargs
print 'password:', env.password
run('echo hello')
#task
def prompt(task_name, *args, **kwargs):
env.password = getpass.getpass('sudo password: ')
execute(task_name, *args, role='my_role', **kwargs)
Note that you can even combine this with parallel execution and the prompt task still only runs once, while the deploy task runs for each host in the role, in parallel.
Finally, an example of how you would invoke it:
$ fab prompt:deploy,some_arg,another_arg,key=value
Seems like sudo may not be that bad of an option after all. You can specify which commands a user can run and the arguments the command may take (man sudoers). If the problem is just having to type the password, an option would involve using the pexpect module to login automatically, maybe with a password that you could store encrypted:
import pexpect, sys
pwd = getEncryptedPassword()
cmd = "yourcommand"
sCmd = pexpect.spawn('sudo {0}'.format(cmd))
sCmd.logfile_read = sys.stdout
sCmd.expect('Password:')
sCmd.sendline(pwd)
sCmd.expect(pexpect.EOF)
Use the keyring module to store and access passwords securely.
Here's how I do it with Fabric 2:
from fabric import task
import keyring
#task
def restart_apache(connection):
# set the password with keyring.set_password('some-host', 'some-user', 'passwd')
connection.config.sudo.password = keyring.get_password(connection.host, 'some-user')
connection.sudo('service apache2 restart')
You could also use GPG or any other command-line password tool. For example:
connection.config.sudo.password = connection.local('gpg --quiet -d /path/to/secret.gpg', hide=True).strip()
The secret.gpg file can be generated with echo "mypassword" | gpg -e > secret.gpg. The hide argument avoids echoing the password to the console.
To retain support for --prompt-for-sudo-password, add a conditional:
if not connection.config.sudo.password:
connection.config.sudo.password = keyring.get_password(connection.host, 'some-user')
You can also use passwords for multiple machines:
from fabric import env
env.hosts = ['user1#host1:port1', 'user2#host2.port2']
env.passwords = {'user1#host1:port1': 'password1', 'user2#host2.port2': 'password2'}
See this answer: https://stackoverflow.com/a/5568219/552671
As Bartek also suggests, enable password-less sudo for the deployment 'user' in the sudoers file.
Something like:
run('echo "{0} ALL=(ALL) ALL" >> /etc/sudoers'.format(env.user))
I am use fabric to write my rsync wrapper, the variable env.host_string will be set by execute() to run task. To get env.host_string, I run('pwd') first, and run rsync.
Is It possible to make sure user set env.hosts before some checkpoint arrived, such as the condition src == env.host_string ?
from fabric.api import run, env, task, abort
from string import Template
#task
def sync_to_same_dir(src, path):
env.host_string
cmd_template = Template("""rsync --dry-run -e ssh -avr $user#$src_host:$path/ $path/""")
if path.endswith("/"):
path = path[:-1]
run("pwd") # it's a work around
if src == env.host_string:
abort("cannot rysnc to the same host")
cmd = cmd_template.substitute(path=path, user=env.user, src_host=src)
run(cmd)
I find the answer from fabric's source code. There is a simple idea: how run check host as needed ?
#needs_host
def put(local_path=None, remote_path=None, use_sudo=False,
mirror_local_mode=False, mode=None):
"""
Upload one or more files to a remote host.
"""
I trace the needs_host it will prompt to ask hosts, when the user don't assign any hosts:
No hosts found. Please specify (single) host string for connection:
We can rewrite the task as:
from fabric.network import needs_host
#task
#needs_host
def the_task_needs_host(): pass
What are you trying to do? A task knows what host it's using w/o having any other fabric calls:
fab -f host-test.py foo
[98.98.98.98] Executing task 'foo'
98.98.98.98
98.98.98.98
Done.
And here is the script eg:
#!/user/bin/env python
from fabric.api import *
env.user = 'mgoose'
#task
#hosts("98.98.98.98")
def foo():
print(env.host)
print(env.host_string)
So you don't have to do anything special to know what host your task is on.