Commit 6a879686 authored by Jürgen Haas's avatar Jürgen Haas
Browse files

Merge branch 'release/2016-10-30-15-49'

parents a8e61c38 ec519d7c
Pipeline #1040 failed with stage
in 1 minute and 54 seconds
......@@ -213,8 +213,6 @@ cd /usr/local/bin
sudo ln -s /opt/ansible/directory/ansible.py a
sudo ln -s /opt/ansible/directory/ansible-playbook.py apb
sudo ln -s /opt/ansible/directory/ansible-script.py ascr
sudo ln -s /opt/ansible/directory/role.py arole
sudo ln -s /opt/ansible/directory/sanity.py asanity
```
Since version 1.2, the setup script is creating those links by default for you.
......
......@@ -5,7 +5,6 @@ Wrapper for the official Ansible Playbook application
=====================================================
'''
import os
import argparse
from lib import general
......
......@@ -9,11 +9,53 @@ to be executed as the first argument.
'''
import argparse
import sys
import os
import yaml
from tabulate import tabulate
from lib import general
path = os.path.dirname(os.path.realpath(__file__)) + os.path.sep + 'scripts' + os.path.sep
def list():
scripts = []
for file in sorted(os.listdir(path)):
name, ext = os.path.splitext(file)
if ext == '.yml':
with open(path + file, 'r') as config:
try:
script = yaml.load(config)
scripts.append([name, script['description']])
except yaml.YAMLError:
pass
print(tabulate(scripts, headers=['Script', 'Description']))
exit(0)
def script():
l = len(sys.argv)
if l == 1:
list()
arg = sys.argv[1]
if arg[0] == '-':
raise Exception('First arguments needs to be a script name.')
if arg == 'list':
list()
with open(path + arg + '.yml', 'r') as config:
try:
return yaml.load(config)
except Exception:
pass
raise Exception('Script %s does not exist.' % (arg))
parser = argparse.ArgumentParser(description='Ansible script by Paragon',
epilog='Use *ansible-script list* to get a list of all available scripts')
parser.add_argument('script',
help='Name of the prepared script to be executed. Use "list" to see all available scripts')
general.run(parser, general.find_script(True))
general.run(parser, script())
......@@ -9,7 +9,5 @@ import argparse
from lib import general
parser = argparse.ArgumentParser(description='Ansible wrapper by Paragon')
parser.add_argument('--script', default=None,
help='Call one of the prepared scripts')
general.run(parser, general.find_script(False))
general.run(parser)
......@@ -2,174 +2,18 @@ import os
import json
import yaml
import uservars
import sys
import importlib
import threading
import Queue
import types
import window
from subprocess import Popen, PIPE, STDOUT
try:
import Tkinter as tk
import ttk
guiAvailable = True
except ImportError:
guiAvailable = False
queue = None
error_seen = False
class GuiPart:
defaultColor = '#7F7F7F'
ccn = {
u'0': defaultColor,
u'0;30': '#000000',
u'0;31': '#FF0000',
u'0;32': '#00FF00',
u'0;33': '#FFFF00',
u'0;34': '#0000FF',
u'0;35': '#FF00FF',
u'0;36': '#00FFFF',
u'0;37': '#808080',
u'1;30': '#404040',
u'1;31': '#FF4500',
u'1;32': '#ADFF2F',
u'1;33': '#FFF68F',
u'1;34': '#1E90FF',
u'1;35': '#FF83FA',
u'1;36': '#97FFFF',
u'1;37': '#FFFFFF',
}
prefix = '['
suffix = 'm'
tabs = []
def __init__(self, queue):
self.queue = queue
self.master = tk.Tk()
self.master.title('Ansible')
self.notebook = ttk.Notebook(self.master)
self.newCompany()
#TODO: Add scrollbars
#sx = tk.Scrollbar(self.master, orient=tk.HORIZONTAL)
#sx.pack(side=tk.BOTTOM, fill=tk.X)
#sx.config(command=self.t.xview)
#sy = tk.Scrollbar(self.master)
#sy.pack(side=tk.RIGHT, fill=tk.Y)
#sy.config(command=self.t.yview)
#self.t.config(yscrollcommand=sy.set)
#self.t.config(xscrollcommand=sx.set)
self.notebook.pack()
self.master.update_idletasks()
self.periodicCall()
def newCompany(self, name=None):
frame = tk.Frame(self.notebook)
t = tk.Text(frame, bg='black', wrap=tk.NONE)
t.pack(side=tk.LEFT, fill=tk.BOTH)
if name:
self.notebook.add(frame, text=name)
else:
self.notebook.add(frame)
tab = {
'id': (self.notebook.index('end') - 1),
'name': name,
'text': t,
}
self.tabs.append(tab)
return tab
def getCompany(self, name):
for tab in self.tabs:
if tab['name'] and tab['name'] == name:
return tab
if not tab['name']:
tab['name'] = name
self.notebook.tab(tab['id'], text=name)
return tab
return self.newCompany(name)
def onResize(self, event):
self.notebook.pack(fill=tk.BOTH, expand=tk.YES)
for tab in self.tabs:
tab['text'].pack(fill=tk.BOTH, expand=tk.YES)
def periodicCall(self):
self.processIncoming()
self.master.after(100, self.periodicCall)
def show(self):
self.master.bind('<Configure>', self.onResize)
self.master.mainloop()
def processIncoming(self):
while self.queue.qsize():
try:
item = self.queue.get(0)
tab = self.getCompany(item['company'])
if 'completed' in item:
msg = '###Completed###'
label = "[ %s ]*" % (tab['name'])
self.notebook.tab(tab['id'], text=label)
else:
msg = item['msg']
color = self.defaultColor
while True:
output = msg
next = msg.find(self.prefix)
if next > 0:
output = msg[:next]
msg = msg[next:]
else:
for c in self.ccn:
ansi = "%s%s%s" % (self.prefix, c, self.suffix)
if msg.find(ansi) == 0:
output = ''
msg = msg[len(ansi):]
color = self.ccn[c]
if output:
tag = color.replace(' ', '_')
tab['text'].tag_config(tag, foreground=color)
tab['text'].insert(tk.END, output, tag)
tab['text'].see(tk.END)
tab['text'].update_idletasks()
if output == msg:
break
except Queue.Empty:
pass
def find_script(required):
l = len(sys.argv)
scriptname = None
if l == 1:
return None
for i in range(1, l):
arg = sys.argv[i]
if required:
if arg[0] != '-':
scriptname = arg
else:
if '--script' == arg:
if l > i+1:
scriptname = sys.argv[i+1]
elif '--script=' in arg:
scriptname = arg[9:]
if scriptname:
return importlib.import_module('.' + scriptname, package='scripts')
return None
def run(parser, script=None):
global guiAvailable, queue
global queue
env = os.environ.copy()
path = os.path.realpath(os.path.dirname(os.path.realpath(__file__)) + os.path.sep + '..') + os.path.sep
......@@ -177,29 +21,42 @@ def run(parser, script=None):
parser.add_argument('--limit', default='all',
help='Host name or pattern')
if guiAvailable:
if window.guiAvailable:
parser.add_argument('--no-gui', action='store_true', default=False,
help='Disable GUI')
if not os.path.exists(path + 'inventory/group_vars'):
parser.add_argument('--company', default=os.environ.get('ANSIBLE_COMPANY', None),
required=(not isGitlabUser(env)),
required=companyRequired(env),
help='Only useful if inventories from multiple companies are installed')
parser.add_argument('--include-local', action='store_true', default=False,
help='If company=all, this would also include the local inventory, default to False')
if script:
parser.description = script.__doc__
parser.description = script['description']
parser.add_argument('--custom', action='store_true', default=False,
help='Only run a custom playbook and ignore the global one')
script.extend(parser)
if 'cli' in script:
for type in ['arguments', 'options']:
prefix = '--' if type == 'options' else ''
if type in script['cli']:
for item in script['cli'][type]:
parser.add_argument(prefix + item, **script['cli'][type][item])
if 'defaults' in script['cli']:
for item in script['cli']['defaults']:
parser.set_defaults(**{item: script['cli']['defaults'][item]})
args, extras = parser.parse_known_args()
if guiAvailable and args.no_gui:
guiAvailable = False
if window.guiAvailable and args.no_gui:
window.guiAvailable = False
if script:
script.append(args)
if 'arguments' in script:
for item in script['arguments']:
value = script['arguments'][item]
if not isinstance(value, types.StringTypes):
value = ','.join(value)
setattr(args, item, value)
if 'playbook' in args and 'limit' in args:
extras.append('--limit=' + args.limit)
......@@ -249,49 +106,64 @@ def run(parser, script=None):
if 'group_vars' in files:
# This is a single inventory installation, call it just once
main(args, extras, path, pathSecrets, None, script)
followUp(script, args)
elif not company:
raise Exception('No company given')
else:
if guiAvailable:
if window.guiAvailable:
queue = Queue.Queue()
guiAvailable = GuiPart(queue)
threads = []
main_started = 0
companies = company.split(',')
excluded = ['.git', 'cloud']
if not args.include_local and not 'local' in companies:
excluded.append('local')
for file in files:
if file in excluded or os.path.isfile(path + 'inventory' + os.path.sep + file):
continue
if 'all' in companies or file in companies:
main_started += 1
thread = threading.Thread(target=main, args=(args, extras, path, pathSecrets, file, script))
thread.start()
threads.append(thread)
if main_started == 0:
raise Exception('No valid company given')
if guiAvailable:
guiAvailable.show()
window.guiAvailable = window.GuiPart(queue, startThreads, company, args, files, path, extras, pathSecrets, script)
window.guiAvailable.main()
else:
for thread in threads:
thread.join()
startThreads(company, args, files, path, extras, pathSecrets, script)
followUp(script, args)
def followUp(script, args):
global error_seen
if error_seen:
exit(1)
if script and hasattr(script, 'follower'):
cmd = ['ascr', script.follower(), '--limit=' + args.limit]
if 'follower' in script:
cmd = ['ascr', script['follower'], '--limit=' + args.limit]
if hasattr(args, 'company'):
cmd.append('--company=' + args.company)
if not guiAvailable:
if not window.guiAvailable:
cmd.append('--no-gui')
Popen(cmd)
def startThreads(company, args, files, path, extras, pathSecrets, script):
threads = []
main_started = 0
companies = company.split(',')
excluded = ['.git', 'cloud']
if not args.include_local and not 'local' in companies:
excluded.append('local')
for file in files:
if file in excluded or os.path.isfile(path + 'inventory' + os.path.sep + file):
continue
if 'all' in companies or file in companies:
main_started += 1
thread = threading.Thread(target=main, args=(args, extras, path, pathSecrets, file, script))
thread.start()
threads.append(thread)
if main_started == 0:
raise Exception('No valid company given')
if window.guiAvailable:
pass
else:
for thread in threads:
thread.join()
def companyRequired(env):
if 'ANSIBLE_COMPANY' in env:
return False
return not isGitlabUser(env)
def isGitlabUser(env):
return ('USER' in env and env['USER'] == 'gitlab-runner')
......@@ -357,7 +229,21 @@ def main(args, extras, path, pathSecrets, company = None, script = None):
cmd.append('--extra-vars={excluded_roles: ' + json.dumps(excluded_roles) + '}')
if script:
script.build(cmd, args)
if 'command' in script:
for item in script['command']:
condition = True
if 'condition' in item:
condition = bool(getattr(args, item['condition']))
if condition:
value = item['value']
if not isinstance(value, types.StringTypes):
value = ','.join(value)
if 'args' in item:
commandargs = []
for commandarg in item['args']:
commandargs.append(getattr(args, commandarg))
value = value % tuple(commandargs)
cmd.append('--' + item['name'] + '=' + value)
# Append more CLI options
for extra in extras:
......@@ -394,9 +280,9 @@ def play(playbook, args, env, path):
def subprocess(cmd, path, env):
global error_seen, guiAvailable, queue
global error_seen, queue
if guiAvailable:
if window.guiAvailable:
process = Popen(cmd, cwd=path, env=env, stdout=PIPE, stderr=STDOUT)
while True:
......
import os
import Queue
try:
import pygtk
pygtk.require('2.0')
import gobject
import gtk
guiAvailable = True
except ImportError:
guiAvailable = False
class GuiPart(gtk.Window):
defaultColor = '#7F7F7F'
ccn = {
u'0': defaultColor,
u'0;30': '#000000',
u'0;31': '#FF0000',
u'0;32': '#00FF00',
u'0;33': '#FFFF00',
u'0;34': '#0000FF',
u'0;35': '#FF00FF',
u'0;36': '#00FFFF',
u'0;37': '#808080',
u'1;30': '#404040',
u'1;31': '#FF4500',
u'1;32': '#ADFF2F',
u'1;33': '#FFF68F',
u'1;34': '#1E90FF',
u'1;35': '#FF83FA',
u'1;36': '#97FFFF',
u'1;37': '#FFFFFF',
}
prefix = '['
suffix = 'm'
tabs = []
def __init__(self, queue, startThreads, company, args, files, path, extras, pathSecrets, script):
gobject.threads_init()
self.queue = queue
gtk.Window.__init__(self)
self.set_title('Ansible')
self.set_icon_from_file(os.path.dirname(os.path.realpath(__file__)) + os.path.sep + 'ansible.ico')
self.set_default_size(640, 468)
self.connect("delete-event", gtk.main_quit)
self.connect('key-press-event', self.on_key_press)
self.keymap = gtk.gdk.keymap_get_default()
table = gtk.Table(3, 6, False)
self.add(table)
self.notebook = gtk.Notebook()
table.attach(self.notebook, 0, 6, 0, 1)
self.notebook.show()
table.show()
self.periodicCall()
startThreads(company, args, files, path, extras, pathSecrets, script)
def main(self):
self.show_all()
gtk.main()
def on_key_press(self, window, event):
"""Handle a keyboard event"""
mapping = self.key_mapping(event)
if mapping:
if mapping == 'full_screen':
self.set_fullscreen(not self.isfullscreen)
elif mapping == 'close_window':
gtk.main_quit(window, event)
elif mapping == 'new_tab':
self.tab_new(self.get_focussed_terminal())
else:
return(False)
return(True)
def key_mapping(self, event):
"""Translate a keyboard event into a mapped key"""
try:
keyval, _egp, _lvl, consumed = self.keymap.translate_keyboard_state(
event.hardware_keycode,
event.state & ~gtk.gdk.LOCK_MASK,
event.group)
except TypeError:
return None
if gtk.gdk.CONTROL_MASK & event.state:
if keyval == 113:# q
return 'close_window'
return False
def newCompany(self, name):
frame = gtk.Frame()
s = gtk.ScrolledWindow()
t = gtk.TextView()
t.modify_base(gtk.STATE_NORMAL, gtk.gdk.Color(255,0,0))
t.set_editable(False)
t.set_cursor_visible(False)
t.show()
s.add(t)
s.show()
frame.add(s)
frame.show()
if name:
self.notebook.append_page(frame, gtk.Label(name))
else:
self.notebook.append_page(frame, gtk.Label(''))
tab = {
'id': (self.notebook.get_n_pages() - 1),
'name': name,
'text': t,
'tags': {},
}
self.tabs.append(tab)
return tab
def getCompany(self, name):
for tab in self.tabs:
if tab['name'] and tab['name'] == name:
return tab
if not tab['name']:
tab['name'] = name
self.notebook.set_tab_label_text(self.notebook.get_nth_page(tab['id']), name)
return tab
return self.newCompany(name)
def periodicCall(self):
self.processIncoming()
gobject.timeout_add(100, self.periodicCall)
def processIncoming(self):
while self.queue.qsize():
try:
item = self.queue.get(0)
tab = self.getCompany(item['company'])
if 'completed' in item:
msg = '###Completed###'
label = "[ %s ]*" % (tab['name'])
self.notebook.set_tab_label_text(self.notebook.get_nth_page(tab['id']), label)
else:
msg = item['msg']
color = self.defaultColor
while True:
output = msg
next = msg.find(self.prefix)
if next > 0:
output = msg[:next]
msg = msg[next:]
else:
for c in self.ccn:
ansi = "%s%s%s" % (self.prefix, c, self.suffix)
if msg.find(ansi) == 0:
output = ''
msg = msg[len(ansi):]
color = self.ccn[c]
if output:
buffer = tab['text'].get_buffer()
tags = tab['tags']
position = buffer.get_end_iter()
tag = color.replace(' ', '_')
if tag not in tags:
tags[tag] = buffer.create_tag(tag, foreground=color)
buffer.insert_with_tags(position, output, tags[tag])
else: