mirror of
https://github.com/ansible/awx.git
synced 2026-01-12 18:40:01 -03:30
Jobs updates to start/cancel and prompt for passwords via the admin.
This commit is contained in:
parent
b2c4ca6ece
commit
52d31d105d
@ -327,11 +327,14 @@ class JobAdmin(BaseModelAdmin):
|
||||
(_('More Options'), {'fields': ('use_sudo', 'forks', 'limit',
|
||||
'verbosity', 'extra_vars'),
|
||||
'classes': ('collapse',)}),
|
||||
(_('Start/Cancel Job'), {'fields': ('start_job',)}),
|
||||
(_('Start Job'), {'fields': ('start_job', 'ssh_password',
|
||||
'sudo_password', 'ssh_key_unlock')}),
|
||||
#(_('Cancel Job'), {'fields': ('cancel_job',)}),
|
||||
#(_('Tags'), {'fields': ('tags',)}),
|
||||
(_('Audit Trail'), {'fields': ('creation_date', 'created_by',
|
||||
'audit_trail',)}),
|
||||
(_('Job Status'), {'fields': ('status', 'get_result_stdout_display',
|
||||
(_('Job Status'), {'fields': (('status', 'cancel_job'),
|
||||
'get_result_stdout_display',
|
||||
'get_result_stderr_display',
|
||||
'get_result_traceback_display',
|
||||
'celery_task_id')}),
|
||||
@ -358,15 +361,16 @@ class JobAdmin(BaseModelAdmin):
|
||||
if not obj or not obj.pk or obj.status == 'new':
|
||||
fsets = [fs for fs in fsets if
|
||||
'creation_date' not in fs[1]['fields'] and
|
||||
'status' not in fs[1]['fields']]
|
||||
elif obj and obj.pk and obj.status != 'new':
|
||||
#print obj, obj.pk, obj.status
|
||||
'celery_task_id' not in fs[1]['fields']]
|
||||
if not obj or (obj and obj.pk and obj.status != 'new'):
|
||||
fsets = [fs for fs in fsets if 'start_job' not in fs[1]['fields']]
|
||||
#for fs in fsets:
|
||||
# # FIXME: Show start job on add view
|
||||
# if 'start_job' in fs[1]['fields']:
|
||||
# fs[1]['fields'] = [x for x in fs[1]['fields']
|
||||
# if x != 'start_job']
|
||||
if not obj or (obj and obj.pk and obj.status not in ('pending', 'running')):
|
||||
for fs in fsets:
|
||||
if 'celery_task_id' in fs[1]['fields']:
|
||||
fs[1]['fields'] = ('status', 'get_result_stdout_display',
|
||||
'get_result_stderr_display',
|
||||
'get_result_traceback_display',
|
||||
'celery_task_id')
|
||||
return fsets
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
@ -406,6 +410,7 @@ class JobAdmin(BaseModelAdmin):
|
||||
get_result_traceback_display.short_description = _('Traceback')
|
||||
get_result_traceback_display.allow_tags = True
|
||||
|
||||
|
||||
# FIXME: Add the rest of the models...
|
||||
|
||||
admin.site.register(Organization, OrganizationAdmin)
|
||||
|
||||
@ -89,6 +89,11 @@ class JobAdminForm(JobTemplateAdminForm):
|
||||
'''Custom admin form for creating Jobs.'''
|
||||
|
||||
start_job = forms.BooleanField(initial=False, required=False)
|
||||
ssh_password = forms.CharField(label=_('SSH password'), required=False)
|
||||
sudo_password = forms.CharField(required=False)
|
||||
ssh_key_unlock = forms.CharField(label=_('SSH key passphrase'),
|
||||
required=False)
|
||||
cancel_job = forms.BooleanField(initial=False, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Job
|
||||
@ -97,19 +102,45 @@ class JobAdminForm(JobTemplateAdminForm):
|
||||
super(JobAdminForm, self).__init__(*args, **kwargs)
|
||||
if self.instance.pk and self.instance.status != 'new':
|
||||
self.fields.pop('playbook', None)
|
||||
if (not self.data or self.data.get('start_job', '')) and \
|
||||
self.instance.credential and self.instance.status == 'new':
|
||||
for field in self.instance.get_passwords_needed_to_start():
|
||||
if field not in self.fields:
|
||||
continue
|
||||
self.fields[field].required = True
|
||||
|
||||
def clean_start_job(self):
|
||||
return self.cleaned_data.get('start_job', False)
|
||||
|
||||
def clean_cancel_job(self):
|
||||
return self.cleaned_data.get('cancel_job', False)
|
||||
|
||||
def clean(self):
|
||||
if self.instance.credential and self.instance.status == 'new':
|
||||
for field in self.instance.get_passwords_needed_to_start():
|
||||
if field in self.fields:
|
||||
self.fields[field].required = True
|
||||
return super(JobAdminForm, self).clean()
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super(JobAdminForm, self).save(commit)
|
||||
save_m2m = getattr(self, 'save_m2m', lambda: None)
|
||||
should_start = bool(self.cleaned_data.get('start_job', '') and
|
||||
instance.status == 'new')
|
||||
start_opts = {}
|
||||
for field in ('ssh_password', 'sudo_password', 'ssh_key_unlock'):
|
||||
value = self.cleaned_data.get(field, '')
|
||||
if value:
|
||||
start_opts[field] = value
|
||||
#print 'should_start', should_start
|
||||
should_cancel = bool(self.cleaned_data.get('cancel_job', '') and
|
||||
instance.status in ('new', 'pending'))
|
||||
def new_save_m2m():
|
||||
save_m2m()
|
||||
if should_start:
|
||||
instance.start()
|
||||
instance.start(**start_opts)
|
||||
if should_cancel:
|
||||
instance.cancel()
|
||||
if commit:
|
||||
new_save_m2m()
|
||||
else:
|
||||
|
||||
@ -901,8 +901,7 @@ class JobTemplate(CommonModel):
|
||||
'''
|
||||
Create a new job based on this template.
|
||||
'''
|
||||
start_job = kwargs.pop('start', False)
|
||||
save_job = kwargs.pop('save', True) or start_job # Start implies save.
|
||||
save_job = kwargs.pop('save', True)
|
||||
kwargs['job_template'] = self
|
||||
kwargs.setdefault('name', '%s %s' % (self.name, now().isoformat()))
|
||||
kwargs.setdefault('description', self.description)
|
||||
@ -919,8 +918,6 @@ class JobTemplate(CommonModel):
|
||||
job = Job(**kwargs)
|
||||
if save_job:
|
||||
job.save()
|
||||
if start_job:
|
||||
job.start()
|
||||
return job
|
||||
|
||||
# project has one default playbook but really should have a list of playbooks and flags ...
|
||||
@ -1137,14 +1134,22 @@ class Job(CommonModel):
|
||||
except TaskMeta.DoesNotExist:
|
||||
pass
|
||||
|
||||
def get_passwords_needed_to_start(self):
|
||||
'''Return list of password field names needed to start the job.'''
|
||||
needed = []
|
||||
for field in ('ssh_password', 'sudo_password', 'ssh_key_unlock'):
|
||||
if self.credential and getattr(self.credential, 'needs_%s' % field):
|
||||
needed.append(field)
|
||||
return needed
|
||||
|
||||
def start(self, **kwargs):
|
||||
from lib.main.tasks import RunJob
|
||||
if self.status != 'new':
|
||||
return False
|
||||
|
||||
#username = kwargs.get('username', self.username)
|
||||
|
||||
opts = {}
|
||||
needed = self.get_passwords_needed_to_start()
|
||||
opts = dict([(field, kwargs.get(field, '')) for field in needed])
|
||||
if not all(opts.values()):
|
||||
return False
|
||||
self.status = 'pending'
|
||||
self.save(update_fields=['status'])
|
||||
task_result = RunJob().delay(self.pk, **opts)
|
||||
|
||||
@ -55,6 +55,33 @@ class RunJob(Task):
|
||||
'''
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), *args))
|
||||
|
||||
def build_ssh_key_path(self, job, **kwargs):
|
||||
'''
|
||||
Create a temporary file containing the SSH private key.
|
||||
'''
|
||||
creds = job.credential
|
||||
if creds and creds.ssh_key_data:
|
||||
handle, path = tempfile.mkstemp()
|
||||
f = os.fdopen(handle, 'w')
|
||||
f.write(creds.ssh_key_data)
|
||||
f.close()
|
||||
return path
|
||||
else:
|
||||
return ''
|
||||
|
||||
def build_passwords(self, job, **kwargs):
|
||||
'''
|
||||
Build a dictionary of passwords for SSH private key, SSH user and sudo.
|
||||
'''
|
||||
passwords = {}
|
||||
creds = job.credential
|
||||
if creds:
|
||||
for field in ('ssh_key_unlock', 'ssh_password', 'sudo_password'):
|
||||
value = kwargs.get(field, getattr(creds, field))
|
||||
if value not in ('', 'ASK'):
|
||||
passwords[field] = value
|
||||
return passwords
|
||||
|
||||
def build_env(self, job, **kwargs):
|
||||
'''
|
||||
Build environment dictionary for ansible-playbook.
|
||||
@ -80,18 +107,25 @@ class RunJob(Task):
|
||||
optionally using ssh-agent for public/private key authentication.
|
||||
'''
|
||||
creds = job.credential
|
||||
use_ssh_agent = False
|
||||
ssh_username, sudo_username = '', ''
|
||||
if creds:
|
||||
username = creds.ssh_username
|
||||
sudo_username = creds.sudo_username
|
||||
# FIXME: Do something with creds.
|
||||
ssh_username = kwargs.get('ssh_username', creds.ssh_username)
|
||||
sudo_username = kwargs.get('sudo_username', creds.sudo_username)
|
||||
ssh_username = ssh_username or 'root'
|
||||
sudo_username = sudo_username or 'root'
|
||||
inventory_script = self.get_path_to('management', 'commands',
|
||||
'acom_inventory.py')
|
||||
args = ['ansible-playbook', '-i', inventory_script]
|
||||
if job.job_type == 'check':
|
||||
args.append('--check')
|
||||
args.append('--user=%s' % ssh_username)
|
||||
if 'ssh_password' in kwargs.get('passwords', {}):
|
||||
args.append('--ask-pass')
|
||||
if job.use_sudo:
|
||||
args.append('--sudo')
|
||||
args.append('--sudo-user=%s' % sudo_username)
|
||||
if 'sudo_password' in kwargs.get('passwords', {}):
|
||||
args.append('--ask-sudo-pass')
|
||||
if job.forks: # FIXME: Max limit?
|
||||
args.append('--forks=%d' % job.forks)
|
||||
if job.limit:
|
||||
@ -102,22 +136,16 @@ class RunJob(Task):
|
||||
# FIXME: escaping!
|
||||
extra_vars = ' '.join(['%s=%s' % (str(k), str(v)) for k,v in
|
||||
job.extra_vars.items()])
|
||||
args.append('-e', extra_vars)
|
||||
args.append('--extra-vars=%s' % extra_vars)
|
||||
args.append(job.playbook) # relative path to project.local_path
|
||||
if use_ssh_agent:
|
||||
key_path = 'myrsa' # FIXME
|
||||
cmd = '; '.join([subprocess.list2cmdline(['ssh-add', keypath]),
|
||||
ssh_key_path = kwargs.get('ssh_key_path', '')
|
||||
if ssh_key_path:
|
||||
cmd = '; '.join([subprocess.list2cmdline(['ssh-add', ssh_key_path]),
|
||||
subprocess.list2cmdline(args)])
|
||||
return ['ssh-agent', 'sh', '-c', cmd]
|
||||
else:
|
||||
return args
|
||||
|
||||
def build_passwords(self, job, **kwargs):
|
||||
'''
|
||||
Build a dictionary of passwords for SSH private key, SSH user and sudo.
|
||||
'''
|
||||
return {}
|
||||
|
||||
def capture_subprocess_output(self, proc, timeout=1.0):
|
||||
'''
|
||||
Capture stdout/stderr from the given process until the timeout expires.
|
||||
@ -195,6 +223,7 @@ class RunJob(Task):
|
||||
status, stdout, stderr = 'error', '', ''
|
||||
logfile = cStringIO.StringIO()
|
||||
logfile_pos = logfile.tell()
|
||||
print 'ARGS:', repr(args)
|
||||
child = pexpect.spawn(args[0], args[1:], cwd=cwd, env=env)
|
||||
child.logfile_read = logfile
|
||||
job_canceled = False
|
||||
@ -209,7 +238,7 @@ class RunJob(Task):
|
||||
]
|
||||
result_id = child.expect(expect_list, timeout=2)
|
||||
if result_id == 0:
|
||||
child.sendline(passwords.get('ssh_unlock_key', ''))
|
||||
child.sendline(passwords.get('ssh_key_unlock', ''))
|
||||
elif result_id == 1:
|
||||
child.sendline('')
|
||||
elif result_id == 2:
|
||||
@ -237,17 +266,24 @@ class RunJob(Task):
|
||||
Run the job using ansible-playbook and capture its output.
|
||||
'''
|
||||
job = self.update_job(job_pk, status='running')
|
||||
status, stdout, stderr, tb = 'error', '', '', ''
|
||||
try:
|
||||
status, stdout, stderr, tb = 'error', '', '', ''
|
||||
kwargs['ssh_key_path'] = self.build_ssh_key_path(job, **kwargs)
|
||||
kwargs['passwords'] = self.build_passwords(job, **kwargs)
|
||||
args = self.build_args(job, **kwargs)
|
||||
cwd = job.project.local_path
|
||||
env = self.build_env(job, **kwargs)
|
||||
passwords = self.build_passwords(job, **kwargs)
|
||||
#status, stdout, stderr = self.run_subprocess(job_pk, args, cwd,
|
||||
# env, passwords)
|
||||
status, stdout, stderr = self.run_pexpect(job_pk, args, cwd, env,
|
||||
passwords)
|
||||
kwargs['passwords'])
|
||||
except Exception:
|
||||
tb = traceback.format_exc()
|
||||
finally:
|
||||
if kwargs.get('ssh_key_path', ''):
|
||||
try:
|
||||
os.remove(kwargs['ssh_key_path'])
|
||||
except IOError:
|
||||
pass
|
||||
self.update_job(job_pk, status=status, result_stdout=stdout,
|
||||
result_stderr=stderr, result_traceback=tb)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user