mirror of https://github.com/ansible/ansible.git
Merge remote-tracking branch 'ansible/devel' into devel
commit
47db352725
@ -1,16 +1,12 @@
|
|||||||
[submodule "lib/ansible/modules/core"]
|
[submodule "lib/ansible/modules/core"]
|
||||||
path = lib/ansible/modules/core
|
path = lib/ansible/modules/core
|
||||||
url = https://github.com/ansible/ansible-modules-core.git
|
url = https://github.com/ansible/ansible-modules-core
|
||||||
branch = devel
|
|
||||||
[submodule "lib/ansible/modules/extras"]
|
[submodule "lib/ansible/modules/extras"]
|
||||||
path = lib/ansible/modules/extras
|
path = lib/ansible/modules/extras
|
||||||
url = https://github.com/ansible/ansible-modules-extras.git
|
url = https://github.com/ansible/ansible-modules-extras
|
||||||
branch = devel
|
[submodule "v1/ansible/modules/core"]
|
||||||
[submodule "v2/ansible/modules/core"]
|
path = v1/ansible/modules/core
|
||||||
path = v2/ansible/modules/core
|
url = https://github.com/ansible/ansible-modules-core
|
||||||
url = https://github.com/ansible/ansible-modules-core.git
|
[submodule "v1/ansible/modules/extras"]
|
||||||
branch = devel
|
path = v1/ansible/modules/extras
|
||||||
[submodule "v2/ansible/modules/extras"]
|
url = https://github.com/ansible/ansible-modules-extras
|
||||||
path = v2/ansible/modules/extras
|
|
||||||
url = https://github.com/ansible/ansible-modules-extras.git
|
|
||||||
branch = devel
|
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
sudo: false
|
sudo: false
|
||||||
language: python
|
language: python
|
||||||
env:
|
env:
|
||||||
|
- TOKENV=py24
|
||||||
- TOXENV=py26
|
- TOXENV=py26
|
||||||
- TOXENV=py27
|
- TOXENV=py27
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- deadsnakes
|
||||||
|
packages:
|
||||||
|
- python2.4
|
||||||
install:
|
install:
|
||||||
- pip install tox
|
- pip install tox PyYAML Jinja2 sphinx
|
||||||
script:
|
script:
|
||||||
- tox
|
- if test x"$TOKENV" != x'py24' ; then tox ; fi
|
||||||
|
- if test x"$TOKENV" = x'py24' ; then python2.4 -V && python2.4 -m compileall -fq -x 'module_utils/(a10|rax|openstack|ec2|gce).py' lib/ansible/module_utils ; fi
|
||||||
|
#- make -C docsite all
|
||||||
after_success:
|
after_success:
|
||||||
- coveralls
|
- coveralls
|
||||||
|
@ -1,337 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# (c) 2012, Jan-Piet Mens <jpmens () gmail.com>
|
|
||||||
#
|
|
||||||
# This file is part of Ansible
|
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import textwrap
|
|
||||||
import re
|
|
||||||
import optparse
|
|
||||||
import datetime
|
|
||||||
import subprocess
|
|
||||||
import fcntl
|
|
||||||
import termios
|
|
||||||
import struct
|
|
||||||
|
|
||||||
from ansible import utils
|
|
||||||
from ansible.utils import module_docs
|
|
||||||
import ansible.constants as C
|
|
||||||
from ansible.utils import version
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
MODULEDIR = C.DEFAULT_MODULE_PATH
|
|
||||||
|
|
||||||
BLACKLIST_EXTS = ('.pyc', '.swp', '.bak', '~', '.rpm')
|
|
||||||
IGNORE_FILES = [ "COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION"]
|
|
||||||
|
|
||||||
_ITALIC = re.compile(r"I\(([^)]+)\)")
|
|
||||||
_BOLD = re.compile(r"B\(([^)]+)\)")
|
|
||||||
_MODULE = re.compile(r"M\(([^)]+)\)")
|
|
||||||
_URL = re.compile(r"U\(([^)]+)\)")
|
|
||||||
_CONST = re.compile(r"C\(([^)]+)\)")
|
|
||||||
PAGER = 'less'
|
|
||||||
LESS_OPTS = 'FRSX' # -F (quit-if-one-screen) -R (allow raw ansi control chars)
|
|
||||||
# -S (chop long lines) -X (disable termcap init and de-init)
|
|
||||||
|
|
||||||
def pager_print(text):
|
|
||||||
''' just print text '''
|
|
||||||
print text
|
|
||||||
|
|
||||||
def pager_pipe(text, cmd):
|
|
||||||
''' pipe text through a pager '''
|
|
||||||
if 'LESS' not in os.environ:
|
|
||||||
os.environ['LESS'] = LESS_OPTS
|
|
||||||
try:
|
|
||||||
cmd = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=sys.stdout)
|
|
||||||
cmd.communicate(input=text)
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def pager(text):
|
|
||||||
''' find reasonable way to display text '''
|
|
||||||
# this is a much simpler form of what is in pydoc.py
|
|
||||||
if not sys.stdout.isatty():
|
|
||||||
pager_print(text)
|
|
||||||
elif 'PAGER' in os.environ:
|
|
||||||
if sys.platform == 'win32':
|
|
||||||
pager_print(text)
|
|
||||||
else:
|
|
||||||
pager_pipe(text, os.environ['PAGER'])
|
|
||||||
elif subprocess.call('(less --version) 2> /dev/null', shell = True) == 0:
|
|
||||||
pager_pipe(text, 'less')
|
|
||||||
else:
|
|
||||||
pager_print(text)
|
|
||||||
|
|
||||||
def tty_ify(text):
|
|
||||||
|
|
||||||
t = _ITALIC.sub("`" + r"\1" + "'", text) # I(word) => `word'
|
|
||||||
t = _BOLD.sub("*" + r"\1" + "*", t) # B(word) => *word*
|
|
||||||
t = _MODULE.sub("[" + r"\1" + "]", t) # M(word) => [word]
|
|
||||||
t = _URL.sub(r"\1", t) # U(word) => word
|
|
||||||
t = _CONST.sub("`" + r"\1" + "'", t) # C(word) => `word'
|
|
||||||
|
|
||||||
return t
|
|
||||||
|
|
||||||
def get_man_text(doc):
|
|
||||||
|
|
||||||
opt_indent=" "
|
|
||||||
text = []
|
|
||||||
text.append("> %s\n" % doc['module'].upper())
|
|
||||||
|
|
||||||
desc = " ".join(doc['description'])
|
|
||||||
|
|
||||||
text.append("%s\n" % textwrap.fill(tty_ify(desc), initial_indent=" ", subsequent_indent=" "))
|
|
||||||
|
|
||||||
if 'option_keys' in doc and len(doc['option_keys']) > 0:
|
|
||||||
text.append("Options (= is mandatory):\n")
|
|
||||||
|
|
||||||
for o in sorted(doc['option_keys']):
|
|
||||||
opt = doc['options'][o]
|
|
||||||
|
|
||||||
if opt.get('required', False):
|
|
||||||
opt_leadin = "="
|
|
||||||
else:
|
|
||||||
opt_leadin = "-"
|
|
||||||
|
|
||||||
text.append("%s %s" % (opt_leadin, o))
|
|
||||||
|
|
||||||
desc = " ".join(opt['description'])
|
|
||||||
|
|
||||||
if 'choices' in opt:
|
|
||||||
choices = ", ".join(str(i) for i in opt['choices'])
|
|
||||||
desc = desc + " (Choices: " + choices + ")"
|
|
||||||
if 'default' in opt:
|
|
||||||
default = str(opt['default'])
|
|
||||||
desc = desc + " [Default: " + default + "]"
|
|
||||||
text.append("%s\n" % textwrap.fill(tty_ify(desc), initial_indent=opt_indent,
|
|
||||||
subsequent_indent=opt_indent))
|
|
||||||
|
|
||||||
if 'notes' in doc and len(doc['notes']) > 0:
|
|
||||||
notes = " ".join(doc['notes'])
|
|
||||||
text.append("Notes:%s\n" % textwrap.fill(tty_ify(notes), initial_indent=" ",
|
|
||||||
subsequent_indent=opt_indent))
|
|
||||||
|
|
||||||
|
|
||||||
if 'requirements' in doc and doc['requirements'] is not None and len(doc['requirements']) > 0:
|
|
||||||
req = ", ".join(doc['requirements'])
|
|
||||||
text.append("Requirements:%s\n" % textwrap.fill(tty_ify(req), initial_indent=" ",
|
|
||||||
subsequent_indent=opt_indent))
|
|
||||||
|
|
||||||
if 'examples' in doc and len(doc['examples']) > 0:
|
|
||||||
text.append("Example%s:\n" % ('' if len(doc['examples']) < 2 else 's'))
|
|
||||||
for ex in doc['examples']:
|
|
||||||
text.append("%s\n" % (ex['code']))
|
|
||||||
|
|
||||||
if 'plainexamples' in doc and doc['plainexamples'] is not None:
|
|
||||||
text.append("EXAMPLES:")
|
|
||||||
text.append(doc['plainexamples'])
|
|
||||||
if 'returndocs' in doc and doc['returndocs'] is not None:
|
|
||||||
text.append("RETURN VALUES:")
|
|
||||||
text.append(doc['returndocs'])
|
|
||||||
text.append('')
|
|
||||||
|
|
||||||
return "\n".join(text)
|
|
||||||
|
|
||||||
|
|
||||||
def get_snippet_text(doc):
|
|
||||||
|
|
||||||
text = []
|
|
||||||
desc = tty_ify(" ".join(doc['short_description']))
|
|
||||||
text.append("- name: %s" % (desc))
|
|
||||||
text.append(" action: %s" % (doc['module']))
|
|
||||||
|
|
||||||
for o in sorted(doc['options'].keys()):
|
|
||||||
opt = doc['options'][o]
|
|
||||||
desc = tty_ify(" ".join(opt['description']))
|
|
||||||
|
|
||||||
if opt.get('required', False):
|
|
||||||
s = o + "="
|
|
||||||
else:
|
|
||||||
s = o
|
|
||||||
|
|
||||||
text.append(" %-20s # %s" % (s, desc))
|
|
||||||
text.append('')
|
|
||||||
|
|
||||||
return "\n".join(text)
|
|
||||||
|
|
||||||
def get_module_list_text(module_list):
|
|
||||||
tty_size = 0
|
|
||||||
if os.isatty(0):
|
|
||||||
tty_size = struct.unpack('HHHH',
|
|
||||||
fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0)))[1]
|
|
||||||
columns = max(60, tty_size)
|
|
||||||
displace = max(len(x) for x in module_list)
|
|
||||||
linelimit = columns - displace - 5
|
|
||||||
text = []
|
|
||||||
deprecated = []
|
|
||||||
for module in sorted(set(module_list)):
|
|
||||||
|
|
||||||
if module in module_docs.BLACKLIST_MODULES:
|
|
||||||
continue
|
|
||||||
|
|
||||||
filename = utils.plugins.module_finder.find_plugin(module)
|
|
||||||
|
|
||||||
if filename is None:
|
|
||||||
continue
|
|
||||||
if filename.endswith(".ps1"):
|
|
||||||
continue
|
|
||||||
if os.path.isdir(filename):
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
doc, plainexamples, returndocs = module_docs.get_docstring(filename)
|
|
||||||
desc = tty_ify(doc.get('short_description', '?')).strip()
|
|
||||||
if len(desc) > linelimit:
|
|
||||||
desc = desc[:linelimit] + '...'
|
|
||||||
|
|
||||||
if module.startswith('_'): # Handle deprecated
|
|
||||||
deprecated.append("%-*s %-*.*s" % (displace, module[1:], linelimit, len(desc), desc))
|
|
||||||
else:
|
|
||||||
text.append("%-*s %-*.*s" % (displace, module, linelimit, len(desc), desc))
|
|
||||||
except:
|
|
||||||
traceback.print_exc()
|
|
||||||
sys.stderr.write("ERROR: module %s has a documentation error formatting or is missing documentation\n" % module)
|
|
||||||
|
|
||||||
if len(deprecated) > 0:
|
|
||||||
text.append("\nDEPRECATED:")
|
|
||||||
text.extend(deprecated)
|
|
||||||
return "\n".join(text)
|
|
||||||
|
|
||||||
def find_modules(path, module_list):
|
|
||||||
|
|
||||||
if os.path.isdir(path):
|
|
||||||
for module in os.listdir(path):
|
|
||||||
if module.startswith('.'):
|
|
||||||
continue
|
|
||||||
elif os.path.isdir(module):
|
|
||||||
find_modules(module, module_list)
|
|
||||||
elif any(module.endswith(x) for x in BLACKLIST_EXTS):
|
|
||||||
continue
|
|
||||||
elif module.startswith('__'):
|
|
||||||
continue
|
|
||||||
elif module in IGNORE_FILES:
|
|
||||||
continue
|
|
||||||
elif module.startswith('_'):
|
|
||||||
fullpath = '/'.join([path,module])
|
|
||||||
if os.path.islink(fullpath): # avoids aliases
|
|
||||||
continue
|
|
||||||
|
|
||||||
module = os.path.splitext(module)[0] # removes the extension
|
|
||||||
module_list.append(module)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
p = optparse.OptionParser(
|
|
||||||
version=version("%prog"),
|
|
||||||
usage='usage: %prog [options] [module...]',
|
|
||||||
description='Show Ansible module documentation',
|
|
||||||
)
|
|
||||||
|
|
||||||
p.add_option("-M", "--module-path",
|
|
||||||
action="store",
|
|
||||||
dest="module_path",
|
|
||||||
default=MODULEDIR,
|
|
||||||
help="Ansible modules/ directory")
|
|
||||||
p.add_option("-l", "--list",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
dest='list_dir',
|
|
||||||
help='List available modules')
|
|
||||||
p.add_option("-s", "--snippet",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
dest='show_snippet',
|
|
||||||
help='Show playbook snippet for specified module(s)')
|
|
||||||
p.add_option('-v', action='version', help='Show version number and exit')
|
|
||||||
|
|
||||||
(options, args) = p.parse_args()
|
|
||||||
|
|
||||||
if options.module_path is not None:
|
|
||||||
for i in options.module_path.split(os.pathsep):
|
|
||||||
utils.plugins.module_finder.add_directory(i)
|
|
||||||
|
|
||||||
if options.list_dir:
|
|
||||||
# list modules
|
|
||||||
paths = utils.plugins.module_finder._get_paths()
|
|
||||||
module_list = []
|
|
||||||
for path in paths:
|
|
||||||
find_modules(path, module_list)
|
|
||||||
|
|
||||||
pager(get_module_list_text(module_list))
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if len(args) == 0:
|
|
||||||
p.print_help()
|
|
||||||
|
|
||||||
def print_paths(finder):
|
|
||||||
''' Returns a string suitable for printing of the search path '''
|
|
||||||
|
|
||||||
# Uses a list to get the order right
|
|
||||||
ret = []
|
|
||||||
for i in finder._get_paths():
|
|
||||||
if i not in ret:
|
|
||||||
ret.append(i)
|
|
||||||
return os.pathsep.join(ret)
|
|
||||||
|
|
||||||
text = ''
|
|
||||||
for module in args:
|
|
||||||
|
|
||||||
filename = utils.plugins.module_finder.find_plugin(module)
|
|
||||||
if filename is None:
|
|
||||||
sys.stderr.write("module %s not found in %s\n" % (module, print_paths(utils.plugins.module_finder)))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if any(filename.endswith(x) for x in BLACKLIST_EXTS):
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
doc, plainexamples, returndocs = module_docs.get_docstring(filename)
|
|
||||||
except:
|
|
||||||
traceback.print_exc()
|
|
||||||
sys.stderr.write("ERROR: module %s has a documentation error formatting or is missing documentation\n" % module)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if doc is not None:
|
|
||||||
|
|
||||||
all_keys = []
|
|
||||||
for (k,v) in doc['options'].iteritems():
|
|
||||||
all_keys.append(k)
|
|
||||||
all_keys = sorted(all_keys)
|
|
||||||
doc['option_keys'] = all_keys
|
|
||||||
|
|
||||||
doc['filename'] = filename
|
|
||||||
doc['docuri'] = doc['module'].replace('_', '-')
|
|
||||||
doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d')
|
|
||||||
doc['plainexamples'] = plainexamples
|
|
||||||
doc['returndocs'] = returndocs
|
|
||||||
|
|
||||||
if options.show_snippet:
|
|
||||||
text += get_snippet_text(doc)
|
|
||||||
else:
|
|
||||||
text += get_man_text(doc)
|
|
||||||
else:
|
|
||||||
# this typically means we couldn't even parse the docstring, not just that the YAML is busted,
|
|
||||||
# probably a quoting issue.
|
|
||||||
sys.stderr.write("ERROR: module %s missing documentation (or could not parse documentation)\n" % module)
|
|
||||||
pager(text)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -0,0 +1 @@
|
|||||||
|
ansible
|
@ -1,957 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
#
|
|
||||||
# (C) 2013, James Cammarata <jcammarata@ansible.com>
|
|
||||||
#
|
|
||||||
# This file is part of Ansible
|
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
########################################################################
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tarfile
|
|
||||||
import tempfile
|
|
||||||
import urllib
|
|
||||||
import urllib2
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from collections import defaultdict
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
from jinja2 import Environment
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import ansible.constants as C
|
|
||||||
import ansible.utils
|
|
||||||
from ansible.errors import AnsibleError
|
|
||||||
|
|
||||||
default_meta_template = """---
|
|
||||||
galaxy_info:
|
|
||||||
author: {{ author }}
|
|
||||||
description: {{description}}
|
|
||||||
company: {{ company }}
|
|
||||||
# If the issue tracker for your role is not on github, uncomment the
|
|
||||||
# next line and provide a value
|
|
||||||
# issue_tracker_url: {{ issue_tracker_url }}
|
|
||||||
# Some suggested licenses:
|
|
||||||
# - BSD (default)
|
|
||||||
# - MIT
|
|
||||||
# - GPLv2
|
|
||||||
# - GPLv3
|
|
||||||
# - Apache
|
|
||||||
# - CC-BY
|
|
||||||
license: {{ license }}
|
|
||||||
min_ansible_version: {{ min_ansible_version }}
|
|
||||||
#
|
|
||||||
# Below are all platforms currently available. Just uncomment
|
|
||||||
# the ones that apply to your role. If you don't see your
|
|
||||||
# platform on this list, let us know and we'll get it added!
|
|
||||||
#
|
|
||||||
#platforms:
|
|
||||||
{%- for platform,versions in platforms.iteritems() %}
|
|
||||||
#- name: {{ platform }}
|
|
||||||
# versions:
|
|
||||||
# - all
|
|
||||||
{%- for version in versions %}
|
|
||||||
# - {{ version }}
|
|
||||||
{%- endfor %}
|
|
||||||
{%- endfor %}
|
|
||||||
#
|
|
||||||
# Below are all categories currently available. Just as with
|
|
||||||
# the platforms above, uncomment those that apply to your role.
|
|
||||||
#
|
|
||||||
#categories:
|
|
||||||
{%- for category in categories %}
|
|
||||||
#- {{ category.name }}
|
|
||||||
{%- endfor %}
|
|
||||||
dependencies: []
|
|
||||||
# List your role dependencies here, one per line.
|
|
||||||
# Be sure to remove the '[]' above if you add dependencies
|
|
||||||
# to this list.
|
|
||||||
{% for dependency in dependencies %}
|
|
||||||
#- {{ dependency }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
default_readme_template = """Role Name
|
|
||||||
=========
|
|
||||||
|
|
||||||
A brief description of the role goes here.
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
|
|
||||||
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
|
|
||||||
|
|
||||||
Role Variables
|
|
||||||
--------------
|
|
||||||
|
|
||||||
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
|
|
||||||
|
|
||||||
Dependencies
|
|
||||||
------------
|
|
||||||
|
|
||||||
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
|
|
||||||
|
|
||||||
Example Playbook
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
|
|
||||||
|
|
||||||
- hosts: servers
|
|
||||||
roles:
|
|
||||||
- { role: username.rolename, x: 42 }
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
|
|
||||||
BSD
|
|
||||||
|
|
||||||
Author Information
|
|
||||||
------------------
|
|
||||||
|
|
||||||
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
|
|
||||||
"""
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
# Utility functions for parsing actions/options
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
VALID_ACTIONS = ("init", "info", "install", "list", "remove")
|
|
||||||
SKIP_INFO_KEYS = ("platforms","readme_html", "related", "summary_fields", "average_aw_composite", "average_aw_score", "url" )
|
|
||||||
|
|
||||||
def get_action(args):
|
|
||||||
"""
|
|
||||||
Get the action the user wants to execute from the
|
|
||||||
sys argv list.
|
|
||||||
"""
|
|
||||||
for i in range(0,len(args)):
|
|
||||||
arg = args[i]
|
|
||||||
if arg in VALID_ACTIONS:
|
|
||||||
del args[i]
|
|
||||||
return arg
|
|
||||||
return None
|
|
||||||
|
|
||||||
def build_option_parser(action):
|
|
||||||
"""
|
|
||||||
Builds an option parser object based on the action
|
|
||||||
the user wants to execute.
|
|
||||||
"""
|
|
||||||
|
|
||||||
usage = "usage: %%prog [%s] [--help] [options] ..." % "|".join(VALID_ACTIONS)
|
|
||||||
epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])
|
|
||||||
OptionParser.format_epilog = lambda self, formatter: self.epilog
|
|
||||||
parser = OptionParser(usage=usage, epilog=epilog)
|
|
||||||
|
|
||||||
if not action:
|
|
||||||
parser.print_help()
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
# options for all actions
|
|
||||||
# - none yet
|
|
||||||
|
|
||||||
# options specific to actions
|
|
||||||
if action == "info":
|
|
||||||
parser.set_usage("usage: %prog info [options] role_name[,version]")
|
|
||||||
elif action == "init":
|
|
||||||
parser.set_usage("usage: %prog init [options] role_name")
|
|
||||||
parser.add_option(
|
|
||||||
'-p', '--init-path', dest='init_path', default="./",
|
|
||||||
help='The path in which the skeleton role will be created. '
|
|
||||||
'The default is the current working directory.')
|
|
||||||
parser.add_option(
|
|
||||||
'--offline', dest='offline', default=False, action='store_true',
|
|
||||||
help="Don't query the galaxy API when creating roles")
|
|
||||||
elif action == "install":
|
|
||||||
parser.set_usage("usage: %prog install [options] [-r FILE | role_name(s)[,version] | scm+role_repo_url[,version] | tar_file(s)]")
|
|
||||||
parser.add_option(
|
|
||||||
'-i', '--ignore-errors', dest='ignore_errors', action='store_true', default=False,
|
|
||||||
help='Ignore errors and continue with the next specified role.')
|
|
||||||
parser.add_option(
|
|
||||||
'-n', '--no-deps', dest='no_deps', action='store_true', default=False,
|
|
||||||
help='Don\'t download roles listed as dependencies')
|
|
||||||
parser.add_option(
|
|
||||||
'-r', '--role-file', dest='role_file',
|
|
||||||
help='A file containing a list of roles to be imported')
|
|
||||||
elif action == "remove":
|
|
||||||
parser.set_usage("usage: %prog remove role1 role2 ...")
|
|
||||||
elif action == "list":
|
|
||||||
parser.set_usage("usage: %prog list [role_name]")
|
|
||||||
|
|
||||||
# options that apply to more than one action
|
|
||||||
if action != "init":
|
|
||||||
parser.add_option(
|
|
||||||
'-p', '--roles-path', dest='roles_path', default=C.DEFAULT_ROLES_PATH,
|
|
||||||
help='The path to the directory containing your roles. '
|
|
||||||
'The default is the roles_path configured in your '
|
|
||||||
'ansible.cfg file (/etc/ansible/roles if not configured)')
|
|
||||||
|
|
||||||
if action in ("info","init","install"):
|
|
||||||
parser.add_option(
|
|
||||||
'-s', '--server', dest='api_server', default="galaxy.ansible.com",
|
|
||||||
help='The API server destination')
|
|
||||||
|
|
||||||
if action in ("init","install"):
|
|
||||||
parser.add_option(
|
|
||||||
'-f', '--force', dest='force', action='store_true', default=False,
|
|
||||||
help='Force overwriting an existing role')
|
|
||||||
# done, return the parser
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def get_opt(options, k, defval=""):
|
|
||||||
"""
|
|
||||||
Returns an option from an Optparse values instance.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
data = getattr(options, k)
|
|
||||||
except:
|
|
||||||
return defval
|
|
||||||
if k == "roles_path":
|
|
||||||
if os.pathsep in data:
|
|
||||||
data = data.split(os.pathsep)[0]
|
|
||||||
return data
|
|
||||||
|
|
||||||
def exit_without_ignore(options, rc=1):
|
|
||||||
"""
|
|
||||||
Exits with the specified return code unless the
|
|
||||||
option --ignore-errors was specified
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not get_opt(options, "ignore_errors", False):
|
|
||||||
print '- you can use --ignore-errors to skip failed roles.'
|
|
||||||
sys.exit(rc)
|
|
||||||
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
# Galaxy API functions
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def api_get_config(api_server):
|
|
||||||
"""
|
|
||||||
Fetches the Galaxy API current version to ensure
|
|
||||||
the API server is up and reachable.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
url = 'https://%s/api/' % api_server
|
|
||||||
data = json.load(urllib2.urlopen(url))
|
|
||||||
if not data.get("current_version",None):
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return data
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def api_lookup_role_by_name(api_server, role_name, notify=True):
|
|
||||||
"""
|
|
||||||
Uses the Galaxy API to do a lookup on the role owner/name.
|
|
||||||
"""
|
|
||||||
|
|
||||||
role_name = urllib.quote(role_name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
parts = role_name.split(".")
|
|
||||||
user_name = ".".join(parts[0:-1])
|
|
||||||
role_name = parts[-1]
|
|
||||||
if notify:
|
|
||||||
print "- downloading role '%s', owned by %s" % (role_name, user_name)
|
|
||||||
except:
|
|
||||||
parser.print_help()
|
|
||||||
print "- invalid role name (%s). Specify role as format: username.rolename" % role_name
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
url = 'https://%s/api/v1/roles/?owner__username=%s&name=%s' % (api_server,user_name,role_name)
|
|
||||||
try:
|
|
||||||
data = json.load(urllib2.urlopen(url))
|
|
||||||
if len(data["results"]) == 0:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return data["results"][0]
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def api_fetch_role_related(api_server, related, role_id):
|
|
||||||
"""
|
|
||||||
Uses the Galaxy API to fetch the list of related items for
|
|
||||||
the given role. The url comes from the 'related' field of
|
|
||||||
the role.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
url = 'https://%s/api/v1/roles/%d/%s/?page_size=50' % (api_server, int(role_id), related)
|
|
||||||
data = json.load(urllib2.urlopen(url))
|
|
||||||
results = data['results']
|
|
||||||
done = (data.get('next', None) == None)
|
|
||||||
while not done:
|
|
||||||
url = 'https://%s%s' % (api_server, data['next'])
|
|
||||||
print url
|
|
||||||
data = json.load(urllib2.urlopen(url))
|
|
||||||
results += data['results']
|
|
||||||
done = (data.get('next', None) == None)
|
|
||||||
return results
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def api_get_list(api_server, what):
|
|
||||||
"""
|
|
||||||
Uses the Galaxy API to fetch the list of items specified.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
url = 'https://%s/api/v1/%s/?page_size' % (api_server, what)
|
|
||||||
data = json.load(urllib2.urlopen(url))
|
|
||||||
if "results" in data:
|
|
||||||
results = data['results']
|
|
||||||
else:
|
|
||||||
results = data
|
|
||||||
done = True
|
|
||||||
if "next" in data:
|
|
||||||
done = (data.get('next', None) == None)
|
|
||||||
while not done:
|
|
||||||
url = 'https://%s%s' % (api_server, data['next'])
|
|
||||||
print url
|
|
||||||
data = json.load(urllib2.urlopen(url))
|
|
||||||
results += data['results']
|
|
||||||
done = (data.get('next', None) == None)
|
|
||||||
return results
|
|
||||||
except:
|
|
||||||
print "- failed to download the %s list" % what
|
|
||||||
return None
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
# scm repo utility functions
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def scm_archive_role(scm, role_url, role_version, role_name):
|
|
||||||
if scm not in ['hg', 'git']:
|
|
||||||
print "- scm %s is not currently supported" % scm
|
|
||||||
return False
|
|
||||||
tempdir = tempfile.mkdtemp()
|
|
||||||
clone_cmd = [scm, 'clone', role_url, role_name]
|
|
||||||
with open('/dev/null', 'w') as devnull:
|
|
||||||
try:
|
|
||||||
print "- executing: %s" % " ".join(clone_cmd)
|
|
||||||
popen = subprocess.Popen(clone_cmd, cwd=tempdir, stdout=devnull, stderr=devnull)
|
|
||||||
except:
|
|
||||||
raise AnsibleError("error executing: %s" % " ".join(clone_cmd))
|
|
||||||
rc = popen.wait()
|
|
||||||
if rc != 0:
|
|
||||||
print "- command %s failed" % ' '.join(clone_cmd)
|
|
||||||
print " in directory %s" % tempdir
|
|
||||||
return False
|
|
||||||
|
|
||||||
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.tar')
|
|
||||||
if scm == 'hg':
|
|
||||||
archive_cmd = ['hg', 'archive', '--prefix', "%s/" % role_name]
|
|
||||||
if role_version:
|
|
||||||
archive_cmd.extend(['-r', role_version])
|
|
||||||
archive_cmd.append(temp_file.name)
|
|
||||||
if scm == 'git':
|
|
||||||
archive_cmd = ['git', 'archive', '--prefix=%s/' % role_name, '--output=%s' % temp_file.name]
|
|
||||||
if role_version:
|
|
||||||
archive_cmd.append(role_version)
|
|
||||||
else:
|
|
||||||
archive_cmd.append('HEAD')
|
|
||||||
|
|
||||||
with open('/dev/null', 'w') as devnull:
|
|
||||||
print "- executing: %s" % " ".join(archive_cmd)
|
|
||||||
popen = subprocess.Popen(archive_cmd, cwd=os.path.join(tempdir, role_name),
|
|
||||||
stderr=devnull, stdout=devnull)
|
|
||||||
rc = popen.wait()
|
|
||||||
if rc != 0:
|
|
||||||
print "- command %s failed" % ' '.join(archive_cmd)
|
|
||||||
print " in directory %s" % tempdir
|
|
||||||
return False
|
|
||||||
|
|
||||||
shutil.rmtree(tempdir, ignore_errors=True)
|
|
||||||
|
|
||||||
return temp_file.name
|
|
||||||
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
# Role utility functions
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_role_path(role_name, options):
|
|
||||||
"""
|
|
||||||
Returns the role path based on the roles_path option
|
|
||||||
and the role name.
|
|
||||||
"""
|
|
||||||
roles_path = get_opt(options,'roles_path')
|
|
||||||
roles_path = os.path.join(roles_path, role_name)
|
|
||||||
roles_path = os.path.expanduser(roles_path)
|
|
||||||
return roles_path
|
|
||||||
|
|
||||||
def get_role_metadata(role_name, options):
|
|
||||||
"""
|
|
||||||
Returns the metadata as YAML, if the file 'meta/main.yml'
|
|
||||||
exists in the specified role_path
|
|
||||||
"""
|
|
||||||
role_path = os.path.join(get_role_path(role_name, options), 'meta/main.yml')
|
|
||||||
try:
|
|
||||||
if os.path.isfile(role_path):
|
|
||||||
f = open(role_path, 'r')
|
|
||||||
meta_data = yaml.safe_load(f)
|
|
||||||
f.close()
|
|
||||||
return meta_data
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_galaxy_install_info(role_name, options):
|
|
||||||
"""
|
|
||||||
Returns the YAML data contained in 'meta/.galaxy_install_info',
|
|
||||||
if it exists.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
info_path = os.path.join(get_role_path(role_name, options), 'meta/.galaxy_install_info')
|
|
||||||
if os.path.isfile(info_path):
|
|
||||||
f = open(info_path, 'r')
|
|
||||||
info_data = yaml.safe_load(f)
|
|
||||||
f.close()
|
|
||||||
return info_data
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def write_galaxy_install_info(role_name, role_version, options):
|
|
||||||
"""
|
|
||||||
Writes a YAML-formatted file to the role's meta/ directory
|
|
||||||
(named .galaxy_install_info) which contains some information
|
|
||||||
we can use later for commands like 'list' and 'info'.
|
|
||||||
"""
|
|
||||||
|
|
||||||
info = dict(
|
|
||||||
version = role_version,
|
|
||||||
install_date = datetime.datetime.utcnow().strftime("%c"),
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
info_path = os.path.join(get_role_path(role_name, options), 'meta/.galaxy_install_info')
|
|
||||||
f = open(info_path, 'w+')
|
|
||||||
info_data = yaml.safe_dump(info, f)
|
|
||||||
f.close()
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def remove_role(role_name, options):
|
|
||||||
"""
|
|
||||||
Removes the specified role from the roles path. There is a
|
|
||||||
sanity check to make sure there's a meta/main.yml file at this
|
|
||||||
path so the user doesn't blow away random directories
|
|
||||||
"""
|
|
||||||
if get_role_metadata(role_name, options):
|
|
||||||
role_path = get_role_path(role_name, options)
|
|
||||||
shutil.rmtree(role_path)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def fetch_role(role_name, target, role_data, options):
|
|
||||||
"""
|
|
||||||
Downloads the archived role from github to a temp location, extracts
|
|
||||||
it, and then copies the extracted role to the role library path.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# first grab the file and save it to a temp location
|
|
||||||
if '://' in role_name:
|
|
||||||
archive_url = role_name
|
|
||||||
else:
|
|
||||||
archive_url = 'https://github.com/%s/%s/archive/%s.tar.gz' % (role_data["github_user"], role_data["github_repo"], target)
|
|
||||||
print "- downloading role from %s" % archive_url
|
|
||||||
|
|
||||||
try:
|
|
||||||
url_file = urllib2.urlopen(archive_url)
|
|
||||||
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
|
||||||
data = url_file.read()
|
|
||||||
while data:
|
|
||||||
temp_file.write(data)
|
|
||||||
data = url_file.read()
|
|
||||||
temp_file.close()
|
|
||||||
return temp_file.name
|
|
||||||
except Exception, e:
|
|
||||||
# TODO: better urllib2 error handling for error
|
|
||||||
# messages that are more exact
|
|
||||||
print "- error: failed to download the file."
|
|
||||||
return False
|
|
||||||
|
|
||||||
def install_role(role_name, role_version, role_filename, options):
|
|
||||||
# the file is a tar, so open it that way and extract it
|
|
||||||
# to the specified (or default) roles directory
|
|
||||||
|
|
||||||
if not tarfile.is_tarfile(role_filename):
|
|
||||||
print "- error: the file downloaded was not a tar.gz"
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if role_filename.endswith('.gz'):
|
|
||||||
role_tar_file = tarfile.open(role_filename, "r:gz")
|
|
||||||
else:
|
|
||||||
role_tar_file = tarfile.open(role_filename, "r")
|
|
||||||
# verify the role's meta file
|
|
||||||
meta_file = None
|
|
||||||
members = role_tar_file.getmembers()
|
|
||||||
# next find the metadata file
|
|
||||||
for member in members:
|
|
||||||
if "/meta/main.yml" in member.name:
|
|
||||||
meta_file = member
|
|
||||||
break
|
|
||||||
if not meta_file:
|
|
||||||
print "- error: this role does not appear to have a meta/main.yml file."
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
meta_file_data = yaml.safe_load(role_tar_file.extractfile(meta_file))
|
|
||||||
except:
|
|
||||||
print "- error: this role does not appear to have a valid meta/main.yml file."
|
|
||||||
return False
|
|
||||||
|
|
||||||
# we strip off the top-level directory for all of the files contained within
|
|
||||||
# the tar file here, since the default is 'github_repo-target', and change it
|
|
||||||
# to the specified role's name
|
|
||||||
role_path = os.path.join(get_opt(options, 'roles_path'), role_name)
|
|
||||||
role_path = os.path.expanduser(role_path)
|
|
||||||
print "- extracting %s to %s" % (role_name, role_path)
|
|
||||||
try:
|
|
||||||
if os.path.exists(role_path):
|
|
||||||
if not os.path.isdir(role_path):
|
|
||||||
print "- error: the specified roles path exists and is not a directory."
|
|
||||||
return False
|
|
||||||
elif not get_opt(options, "force", False):
|
|
||||||
print "- error: the specified role %s appears to already exist. Use --force to replace it." % role_name
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# using --force, remove the old path
|
|
||||||
if not remove_role(role_name, options):
|
|
||||||
print "- error: %s doesn't appear to contain a role." % role_path
|
|
||||||
print " please remove this directory manually if you really want to put the role here."
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
os.makedirs(role_path)
|
|
||||||
|
|
||||||
# now we do the actual extraction to the role_path
|
|
||||||
for member in members:
|
|
||||||
# we only extract files, and remove any relative path
|
|
||||||
# bits that might be in the file for security purposes
|
|
||||||
# and drop the leading directory, as mentioned above
|
|
||||||
if member.isreg() or member.issym():
|
|
||||||
parts = member.name.split("/")[1:]
|
|
||||||
final_parts = []
|
|
||||||
for part in parts:
|
|
||||||
if part != '..' and '~' not in part and '$' not in part:
|
|
||||||
final_parts.append(part)
|
|
||||||
member.name = os.path.join(*final_parts)
|
|
||||||
role_tar_file.extract(member, role_path)
|
|
||||||
|
|
||||||
# write out the install info file for later use
|
|
||||||
write_galaxy_install_info(role_name, role_version, options)
|
|
||||||
except OSError, e:
|
|
||||||
print "- error: you do not have permission to modify files in %s" % role_path
|
|
||||||
return False
|
|
||||||
|
|
||||||
# return the parsed yaml metadata
|
|
||||||
print "- %s was installed successfully" % role_name
|
|
||||||
return meta_file_data
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
# Action functions
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def execute_init(args, options, parser):
|
|
||||||
"""
|
|
||||||
Executes the init action, which creates the skeleton framework
|
|
||||||
of a role that complies with the galaxy metadata format.
|
|
||||||
"""
|
|
||||||
|
|
||||||
init_path = get_opt(options, 'init_path', './')
|
|
||||||
api_server = get_opt(options, "api_server", "galaxy.ansible.com")
|
|
||||||
force = get_opt(options, 'force', False)
|
|
||||||
offline = get_opt(options, 'offline', False)
|
|
||||||
|
|
||||||
if not offline:
|
|
||||||
api_config = api_get_config(api_server)
|
|
||||||
if not api_config:
|
|
||||||
print "- the API server (%s) is not responding, please try again later." % api_server
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
role_name = args.pop(0).strip()
|
|
||||||
if role_name == "":
|
|
||||||
raise Exception("")
|
|
||||||
role_path = os.path.join(init_path, role_name)
|
|
||||||
if os.path.exists(role_path):
|
|
||||||
if os.path.isfile(role_path):
|
|
||||||
print "- the path %s already exists, but is a file - aborting" % role_path
|
|
||||||
sys.exit(1)
|
|
||||||
elif not force:
|
|
||||||
print "- the directory %s already exists." % role_path
|
|
||||||
print " you can use --force to re-initialize this directory,\n" + \
|
|
||||||
" however it will reset any main.yml files that may have\n" + \
|
|
||||||
" been modified there already."
|
|
||||||
sys.exit(1)
|
|
||||||
except Exception, e:
|
|
||||||
parser.print_help()
|
|
||||||
print "- no role name specified for init"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
ROLE_DIRS = ('defaults','files','handlers','meta','tasks','templates','vars')
|
|
||||||
|
|
||||||
# create the default README.md
|
|
||||||
if not os.path.exists(role_path):
|
|
||||||
os.makedirs(role_path)
|
|
||||||
readme_path = os.path.join(role_path, "README.md")
|
|
||||||
f = open(readme_path, "wb")
|
|
||||||
f.write(default_readme_template)
|
|
||||||
f.close
|
|
||||||
|
|
||||||
for dir in ROLE_DIRS:
|
|
||||||
dir_path = os.path.join(init_path, role_name, dir)
|
|
||||||
main_yml_path = os.path.join(dir_path, 'main.yml')
|
|
||||||
# create the directory if it doesn't exist already
|
|
||||||
if not os.path.exists(dir_path):
|
|
||||||
os.makedirs(dir_path)
|
|
||||||
|
|
||||||
# now create the main.yml file for that directory
|
|
||||||
if dir == "meta":
|
|
||||||
# create a skeleton meta/main.yml with a valid galaxy_info
|
|
||||||
# datastructure in place, plus with all of the available
|
|
||||||
# tags/platforms included (but commented out) and the
|
|
||||||
# dependencies section
|
|
||||||
platforms = []
|
|
||||||
if not offline:
|
|
||||||
platforms = api_get_list(api_server, "platforms") or []
|
|
||||||
categories = []
|
|
||||||
if not offline:
|
|
||||||
categories = api_get_list(api_server, "categories") or []
|
|
||||||
|
|
||||||
# group the list of platforms from the api based
|
|
||||||
# on their names, with the release field being
|
|
||||||
# appended to a list of versions
|
|
||||||
platform_groups = defaultdict(list)
|
|
||||||
for platform in platforms:
|
|
||||||
platform_groups[platform['name']].append(platform['release'])
|
|
||||||
platform_groups[platform['name']].sort()
|
|
||||||
|
|
||||||
inject = dict(
|
|
||||||
author = 'your name',
|
|
||||||
company = 'your company (optional)',
|
|
||||||
license = 'license (GPLv2, CC-BY, etc)',
|
|
||||||
issue_tracker_url = 'http://example.com/issue/tracker',
|
|
||||||
min_ansible_version = '1.2',
|
|
||||||
platforms = platform_groups,
|
|
||||||
categories = categories,
|
|
||||||
)
|
|
||||||
rendered_meta = Environment().from_string(default_meta_template).render(inject)
|
|
||||||
f = open(main_yml_path, 'w')
|
|
||||||
f.write(rendered_meta)
|
|
||||||
f.close()
|
|
||||||
pass
|
|
||||||
elif dir not in ('files','templates'):
|
|
||||||
# just write a (mostly) empty YAML file for main.yml
|
|
||||||
f = open(main_yml_path, 'w')
|
|
||||||
f.write('---\n# %s file for %s\n' % (dir,role_name))
|
|
||||||
f.close()
|
|
||||||
print "- %s was created successfully" % role_name
|
|
||||||
|
|
||||||
def execute_info(args, options, parser):
|
|
||||||
"""
|
|
||||||
Executes the info action. This action prints out detailed
|
|
||||||
information about an installed role as well as info available
|
|
||||||
from the galaxy API.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if len(args) == 0:
|
|
||||||
# the user needs to specify a role
|
|
||||||
parser.print_help()
|
|
||||||
print "- you must specify a user/role name"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
api_server = get_opt(options, "api_server", "galaxy.ansible.com")
|
|
||||||
api_config = api_get_config(api_server)
|
|
||||||
roles_path = get_opt(options, "roles_path")
|
|
||||||
|
|
||||||
for role in args:
|
|
||||||
|
|
||||||
role_info = {}
|
|
||||||
|
|
||||||
install_info = get_galaxy_install_info(role, options)
|
|
||||||
if install_info:
|
|
||||||
if 'version' in install_info:
|
|
||||||
install_info['intalled_version'] = install_info['version']
|
|
||||||
del install_info['version']
|
|
||||||
role_info.update(install_info)
|
|
||||||
|
|
||||||
remote_data = api_lookup_role_by_name(api_server, role, False)
|
|
||||||
if remote_data:
|
|
||||||
role_info.update(remote_data)
|
|
||||||
|
|
||||||
metadata = get_role_metadata(role, options)
|
|
||||||
if metadata:
|
|
||||||
role_info.update(metadata)
|
|
||||||
|
|
||||||
role_spec = ansible.utils.role_spec_parse(role)
|
|
||||||
if role_spec:
|
|
||||||
role_info.update(role_spec)
|
|
||||||
|
|
||||||
if role_info:
|
|
||||||
print "- %s:" % (role)
|
|
||||||
for k in sorted(role_info.keys()):
|
|
||||||
|
|
||||||
if k in SKIP_INFO_KEYS:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(role_info[k], dict):
|
|
||||||
print "\t%s: " % (k)
|
|
||||||
for key in sorted(role_info[k].keys()):
|
|
||||||
if key in SKIP_INFO_KEYS:
|
|
||||||
continue
|
|
||||||
print "\t\t%s: %s" % (key, role_info[k][key])
|
|
||||||
else:
|
|
||||||
print "\t%s: %s" % (k, role_info[k])
|
|
||||||
else:
|
|
||||||
print "- the role %s was not found" % role
|
|
||||||
|
|
||||||
def execute_install(args, options, parser):
|
|
||||||
"""
|
|
||||||
Executes the installation action. The args list contains the
|
|
||||||
roles to be installed, unless -f was specified. The list of roles
|
|
||||||
can be a name (which will be downloaded via the galaxy API and github),
|
|
||||||
or it can be a local .tar.gz file.
|
|
||||||
"""
|
|
||||||
|
|
||||||
role_file = get_opt(options, "role_file", None)
|
|
||||||
|
|
||||||
if len(args) == 0 and role_file is None:
|
|
||||||
# the user needs to specify one of either --role-file
|
|
||||||
# or specify a single user/role name
|
|
||||||
parser.print_help()
|
|
||||||
print "- you must specify a user/role name or a roles file"
|
|
||||||
sys.exit()
|
|
||||||
elif len(args) == 1 and not role_file is None:
|
|
||||||
# using a role file is mutually exclusive of specifying
|
|
||||||
# the role name on the command line
|
|
||||||
parser.print_help()
|
|
||||||
print "- please specify a user/role name, or a roles file, but not both"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
api_server = get_opt(options, "api_server", "galaxy.ansible.com")
|
|
||||||
no_deps = get_opt(options, "no_deps", False)
|
|
||||||
roles_path = get_opt(options, "roles_path")
|
|
||||||
|
|
||||||
roles_done = []
|
|
||||||
if role_file:
|
|
||||||
f = open(role_file, 'r')
|
|
||||||
if role_file.endswith('.yaml') or role_file.endswith('.yml'):
|
|
||||||
roles_left = map(ansible.utils.role_yaml_parse, yaml.safe_load(f))
|
|
||||||
else:
|
|
||||||
# roles listed in a file, one per line
|
|
||||||
roles_left = map(ansible.utils.role_spec_parse, f.readlines())
|
|
||||||
f.close()
|
|
||||||
else:
|
|
||||||
# roles were specified directly, so we'll just go out grab them
|
|
||||||
# (and their dependencies, unless the user doesn't want us to).
|
|
||||||
roles_left = map(ansible.utils.role_spec_parse, args)
|
|
||||||
|
|
||||||
while len(roles_left) > 0:
|
|
||||||
# query the galaxy API for the role data
|
|
||||||
role_data = None
|
|
||||||
role = roles_left.pop(0)
|
|
||||||
role_src = role.get("src")
|
|
||||||
role_scm = role.get("scm")
|
|
||||||
role_path = role.get("path")
|
|
||||||
|
|
||||||
if role_path:
|
|
||||||
options.roles_path = role_path
|
|
||||||
else:
|
|
||||||
options.roles_path = roles_path
|
|
||||||
|
|
||||||
if os.path.isfile(role_src):
|
|
||||||
# installing a local tar.gz
|
|
||||||
tmp_file = role_src
|
|
||||||
else:
|
|
||||||
if role_scm:
|
|
||||||
# create tar file from scm url
|
|
||||||
tmp_file = scm_archive_role(role_scm, role_src, role.get("version"), role.get("name"))
|
|
||||||
elif '://' in role_src:
|
|
||||||
# just download a URL - version will probably be in the URL
|
|
||||||
tmp_file = fetch_role(role_src, None, None, options)
|
|
||||||
else:
|
|
||||||
# installing from galaxy
|
|
||||||
api_config = api_get_config(api_server)
|
|
||||||
if not api_config:
|
|
||||||
print "- the API server (%s) is not responding, please try again later." % api_server
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
role_data = api_lookup_role_by_name(api_server, role_src)
|
|
||||||
if not role_data:
|
|
||||||
print "- sorry, %s was not found on %s." % (role_src, api_server)
|
|
||||||
exit_without_ignore(options)
|
|
||||||
continue
|
|
||||||
|
|
||||||
role_versions = api_fetch_role_related(api_server, 'versions', role_data['id'])
|
|
||||||
if "version" not in role or role['version'] == '':
|
|
||||||
# convert the version names to LooseVersion objects
|
|
||||||
# and sort them to get the latest version. If there
|
|
||||||
# are no versions in the list, we'll grab the head
|
|
||||||
# of the master branch
|
|
||||||
if len(role_versions) > 0:
|
|
||||||
loose_versions = [LooseVersion(a.get('name',None)) for a in role_versions]
|
|
||||||
loose_versions.sort()
|
|
||||||
role["version"] = str(loose_versions[-1])
|
|
||||||
else:
|
|
||||||
role["version"] = 'master'
|
|
||||||
elif role['version'] != 'master':
|
|
||||||
if role_versions and role["version"] not in [a.get('name', None) for a in role_versions]:
|
|
||||||
print 'role is %s' % role
|
|
||||||
print "- the specified version (%s) was not found in the list of available versions (%s)." % (role['version'], role_versions)
|
|
||||||
exit_without_ignore(options)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# download the role. if --no-deps was specified, we stop here,
|
|
||||||
# otherwise we recursively grab roles and all of their deps.
|
|
||||||
tmp_file = fetch_role(role_src, role["version"], role_data, options)
|
|
||||||
installed = False
|
|
||||||
if tmp_file:
|
|
||||||
installed = install_role(role.get("name"), role.get("version"), tmp_file, options)
|
|
||||||
# we're done with the temp file, clean it up
|
|
||||||
if tmp_file != role_src:
|
|
||||||
os.unlink(tmp_file)
|
|
||||||
# install dependencies, if we want them
|
|
||||||
if not no_deps and installed:
|
|
||||||
if not role_data:
|
|
||||||
role_data = get_role_metadata(role.get("name"), options)
|
|
||||||
role_dependencies = role_data['dependencies']
|
|
||||||
else:
|
|
||||||
role_dependencies = role_data['summary_fields']['dependencies'] # api_fetch_role_related(api_server, 'dependencies', role_data['id'])
|
|
||||||
for dep in role_dependencies:
|
|
||||||
if isinstance(dep, basestring):
|
|
||||||
dep = ansible.utils.role_spec_parse(dep)
|
|
||||||
else:
|
|
||||||
dep = ansible.utils.role_yaml_parse(dep)
|
|
||||||
if not get_role_metadata(dep["name"], options):
|
|
||||||
if dep not in roles_left:
|
|
||||||
print '- adding dependency: %s' % dep["name"]
|
|
||||||
roles_left.append(dep)
|
|
||||||
else:
|
|
||||||
print '- dependency %s already pending installation.' % dep["name"]
|
|
||||||
else:
|
|
||||||
print '- dependency %s is already installed, skipping.' % dep["name"]
|
|
||||||
if not tmp_file or not installed:
|
|
||||||
print "- %s was NOT installed successfully." % role.get("name")
|
|
||||||
exit_without_ignore(options)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def execute_remove(args, options, parser):
|
|
||||||
"""
|
|
||||||
Executes the remove action. The args list contains the list
|
|
||||||
of roles to be removed. This list can contain more than one role.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if len(args) == 0:
|
|
||||||
parser.print_help()
|
|
||||||
print '- you must specify at least one role to remove.'
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
for role in args:
|
|
||||||
if get_role_metadata(role, options):
|
|
||||||
if remove_role(role, options):
|
|
||||||
print '- successfully removed %s' % role
|
|
||||||
else:
|
|
||||||
print "- failed to remove role: %s" % role
|
|
||||||
else:
|
|
||||||
print '- %s is not installed, skipping.' % role
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def execute_list(args, options, parser):
|
|
||||||
"""
|
|
||||||
Executes the list action. The args list can contain zero
|
|
||||||
or one role. If one is specified, only that role will be
|
|
||||||
shown, otherwise all roles in the specified directory will
|
|
||||||
be shown.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if len(args) > 1:
|
|
||||||
print "- please specify only one role to list, or specify no roles to see a full list"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if len(args) == 1:
|
|
||||||
# show only the request role, if it exists
|
|
||||||
role_name = args[0]
|
|
||||||
metadata = get_role_metadata(role_name, options)
|
|
||||||
if metadata:
|
|
||||||
install_info = get_galaxy_install_info(role_name, options)
|
|
||||||
version = None
|
|
||||||
if install_info:
|
|
||||||
version = install_info.get("version", None)
|
|
||||||
if not version:
|
|
||||||
version = "(unknown version)"
|
|
||||||
# show some more info about single roles here
|
|
||||||
print "- %s, %s" % (role_name, version)
|
|
||||||
else:
|
|
||||||
print "- the role %s was not found" % role_name
|
|
||||||
else:
|
|
||||||
# show all valid roles in the roles_path directory
|
|
||||||
roles_path = get_opt(options, 'roles_path')
|
|
||||||
roles_path = os.path.expanduser(roles_path)
|
|
||||||
if not os.path.exists(roles_path):
|
|
||||||
parser.print_help()
|
|
||||||
print "- the path %s does not exist. Please specify a valid path with --roles-path" % roles_path
|
|
||||||
sys.exit(1)
|
|
||||||
elif not os.path.isdir(roles_path):
|
|
||||||
print "- %s exists, but it is not a directory. Please specify a valid path with --roles-path" % roles_path
|
|
||||||
parser.print_help()
|
|
||||||
sys.exit(1)
|
|
||||||
path_files = os.listdir(roles_path)
|
|
||||||
for path_file in path_files:
|
|
||||||
if get_role_metadata(path_file, options):
|
|
||||||
install_info = get_galaxy_install_info(path_file, options)
|
|
||||||
version = None
|
|
||||||
if install_info:
|
|
||||||
version = install_info.get("version", None)
|
|
||||||
if not version:
|
|
||||||
version = "(unknown version)"
|
|
||||||
print "- %s, %s" % (path_file, version)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
# The main entry point
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# parse the CLI options
|
|
||||||
action = get_action(sys.argv)
|
|
||||||
parser = build_option_parser(action)
|
|
||||||
(options, args) = parser.parse_args()
|
|
||||||
|
|
||||||
# execute the desired action
|
|
||||||
if 1: #try:
|
|
||||||
fn = globals()["execute_%s" % action]
|
|
||||||
fn(args, options, parser)
|
|
||||||
#except KeyError, e:
|
|
||||||
# print "- error: %s is not a valid action. Valid actions are: %s" % (action, ", ".join(VALID_ACTIONS))
|
|
||||||
# sys.exit(1)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -0,0 +1 @@
|
|||||||
|
ansible
|
@ -1,330 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# (C) 2012, Michael DeHaan, <michael.dehaan@gmail.com>
|
|
||||||
|
|
||||||
# This file is part of Ansible
|
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
#######################################################
|
|
||||||
|
|
||||||
__requires__ = ['ansible']
|
|
||||||
try:
|
|
||||||
import pkg_resources
|
|
||||||
except Exception:
|
|
||||||
# Use pkg_resources to find the correct versions of libraries and set
|
|
||||||
# sys.path appropriately when there are multiversion installs. But we
|
|
||||||
# have code that better expresses the errors in the places where the code
|
|
||||||
# is actually used (the deps are optional for many code paths) so we don't
|
|
||||||
# want to fail here.
|
|
||||||
pass
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import stat
|
|
||||||
|
|
||||||
# Augment PYTHONPATH to find Python modules relative to this file path
|
|
||||||
# This is so that we can find the modules when running from a local checkout
|
|
||||||
# installed as editable with `pip install -e ...` or `python setup.py develop`
|
|
||||||
local_module_path = os.path.abspath(
|
|
||||||
os.path.join(os.path.dirname(__file__), '..', 'lib')
|
|
||||||
)
|
|
||||||
sys.path.append(local_module_path)
|
|
||||||
|
|
||||||
import ansible.playbook
|
|
||||||
import ansible.constants as C
|
|
||||||
import ansible.utils.template
|
|
||||||
from ansible import errors
|
|
||||||
from ansible import callbacks
|
|
||||||
from ansible import utils
|
|
||||||
from ansible.color import ANSIBLE_COLOR, stringc
|
|
||||||
from ansible.callbacks import display
|
|
||||||
|
|
||||||
def colorize(lead, num, color):
|
|
||||||
""" Print 'lead' = 'num' in 'color' """
|
|
||||||
if num != 0 and ANSIBLE_COLOR and color is not None:
|
|
||||||
return "%s%s%-15s" % (stringc(lead, color), stringc("=", color), stringc(str(num), color))
|
|
||||||
else:
|
|
||||||
return "%s=%-4s" % (lead, str(num))
|
|
||||||
|
|
||||||
def hostcolor(host, stats, color=True):
|
|
||||||
if ANSIBLE_COLOR and color:
|
|
||||||
if stats['failures'] != 0 or stats['unreachable'] != 0:
|
|
||||||
return "%-37s" % stringc(host, 'red')
|
|
||||||
elif stats['changed'] != 0:
|
|
||||||
return "%-37s" % stringc(host, 'yellow')
|
|
||||||
else:
|
|
||||||
return "%-37s" % stringc(host, 'green')
|
|
||||||
return "%-26s" % host
|
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
|
||||||
''' run ansible-playbook operations '''
|
|
||||||
|
|
||||||
# create parser for CLI options
|
|
||||||
parser = utils.base_parser(
|
|
||||||
constants=C,
|
|
||||||
usage = "%prog playbook.yml",
|
|
||||||
connect_opts=True,
|
|
||||||
runas_opts=True,
|
|
||||||
subset_opts=True,
|
|
||||||
check_opts=True,
|
|
||||||
diff_opts=True
|
|
||||||
)
|
|
||||||
#parser.add_option('--vault-password', dest="vault_password",
|
|
||||||
# help="password for vault encrypted files")
|
|
||||||
parser.add_option('-t', '--tags', dest='tags', default='all',
|
|
||||||
help="only run plays and tasks tagged with these values")
|
|
||||||
parser.add_option('--skip-tags', dest='skip_tags',
|
|
||||||
help="only run plays and tasks whose tags do not match these values")
|
|
||||||
parser.add_option('--syntax-check', dest='syntax', action='store_true',
|
|
||||||
help="perform a syntax check on the playbook, but do not execute it")
|
|
||||||
parser.add_option('--list-tasks', dest='listtasks', action='store_true',
|
|
||||||
help="list all tasks that would be executed")
|
|
||||||
parser.add_option('--list-tags', dest='listtags', action='store_true',
|
|
||||||
help="list all available tags")
|
|
||||||
parser.add_option('--step', dest='step', action='store_true',
|
|
||||||
help="one-step-at-a-time: confirm each task before running")
|
|
||||||
parser.add_option('--start-at-task', dest='start_at',
|
|
||||||
help="start the playbook at the task matching this name")
|
|
||||||
parser.add_option('--force-handlers', dest='force_handlers',
|
|
||||||
default=C.DEFAULT_FORCE_HANDLERS, action='store_true',
|
|
||||||
help="run handlers even if a task fails")
|
|
||||||
parser.add_option('--flush-cache', dest='flush_cache', action='store_true',
|
|
||||||
help="clear the fact cache")
|
|
||||||
|
|
||||||
options, args = parser.parse_args(args)
|
|
||||||
|
|
||||||
if len(args) == 0:
|
|
||||||
parser.print_help(file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# privlege escalation command line arguments need to be mutually exclusive
|
|
||||||
utils.check_mutually_exclusive_privilege(options, parser)
|
|
||||||
|
|
||||||
if (options.ask_vault_pass and options.vault_password_file):
|
|
||||||
parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive")
|
|
||||||
|
|
||||||
sshpass = None
|
|
||||||
becomepass = None
|
|
||||||
vault_pass = None
|
|
||||||
|
|
||||||
options.ask_vault_pass = options.ask_vault_pass or C.DEFAULT_ASK_VAULT_PASS
|
|
||||||
|
|
||||||
if options.listhosts or options.syntax or options.listtasks or options.listtags:
|
|
||||||
(_, _, vault_pass) = utils.ask_passwords(ask_vault_pass=options.ask_vault_pass)
|
|
||||||
else:
|
|
||||||
options.ask_pass = options.ask_pass or C.DEFAULT_ASK_PASS
|
|
||||||
# Never ask for an SSH password when we run with local connection
|
|
||||||
if options.connection == "local":
|
|
||||||
options.ask_pass = False
|
|
||||||
|
|
||||||
# set pe options
|
|
||||||
utils.normalize_become_options(options)
|
|
||||||
prompt_method = utils.choose_pass_prompt(options)
|
|
||||||
(sshpass, becomepass, vault_pass) = utils.ask_passwords(ask_pass=options.ask_pass,
|
|
||||||
become_ask_pass=options.become_ask_pass,
|
|
||||||
ask_vault_pass=options.ask_vault_pass,
|
|
||||||
become_method=prompt_method)
|
|
||||||
|
|
||||||
# read vault_pass from a file
|
|
||||||
if not options.ask_vault_pass and options.vault_password_file:
|
|
||||||
vault_pass = utils.read_vault_file(options.vault_password_file)
|
|
||||||
|
|
||||||
extra_vars = utils.parse_extra_vars(options.extra_vars, vault_pass)
|
|
||||||
|
|
||||||
only_tags = options.tags.split(",")
|
|
||||||
skip_tags = options.skip_tags
|
|
||||||
if options.skip_tags is not None:
|
|
||||||
skip_tags = options.skip_tags.split(",")
|
|
||||||
|
|
||||||
for playbook in args:
|
|
||||||
if not os.path.exists(playbook):
|
|
||||||
raise errors.AnsibleError("the playbook: %s could not be found" % playbook)
|
|
||||||
if not (os.path.isfile(playbook) or stat.S_ISFIFO(os.stat(playbook).st_mode)):
|
|
||||||
raise errors.AnsibleError("the playbook: %s does not appear to be a file" % playbook)
|
|
||||||
|
|
||||||
inventory = ansible.inventory.Inventory(options.inventory, vault_password=vault_pass)
|
|
||||||
|
|
||||||
# Note: slightly wrong, this is written so that implicit localhost
|
|
||||||
# (which is not returned in list_hosts()) is taken into account for
|
|
||||||
# warning if inventory is empty. But it can't be taken into account for
|
|
||||||
# checking if limit doesn't match any hosts. Instead we don't worry about
|
|
||||||
# limit if only implicit localhost was in inventory to start with.
|
|
||||||
#
|
|
||||||
# Fix this in v2
|
|
||||||
no_hosts = False
|
|
||||||
if len(inventory.list_hosts()) == 0:
|
|
||||||
# Empty inventory
|
|
||||||
utils.warning("provided hosts list is empty, only localhost is available")
|
|
||||||
no_hosts = True
|
|
||||||
inventory.subset(options.subset)
|
|
||||||
if len(inventory.list_hosts()) == 0 and no_hosts is False:
|
|
||||||
# Invalid limit
|
|
||||||
raise errors.AnsibleError("Specified --limit does not match any hosts")
|
|
||||||
|
|
||||||
# run all playbooks specified on the command line
|
|
||||||
for playbook in args:
|
|
||||||
|
|
||||||
stats = callbacks.AggregateStats()
|
|
||||||
playbook_cb = callbacks.PlaybookCallbacks(verbose=utils.VERBOSITY)
|
|
||||||
if options.step:
|
|
||||||
playbook_cb.step = options.step
|
|
||||||
if options.start_at:
|
|
||||||
playbook_cb.start_at = options.start_at
|
|
||||||
runner_cb = callbacks.PlaybookRunnerCallbacks(stats, verbose=utils.VERBOSITY)
|
|
||||||
|
|
||||||
pb = ansible.playbook.PlayBook(
|
|
||||||
playbook=playbook,
|
|
||||||
module_path=options.module_path,
|
|
||||||
inventory=inventory,
|
|
||||||
forks=options.forks,
|
|
||||||
remote_user=options.remote_user,
|
|
||||||
remote_pass=sshpass,
|
|
||||||
callbacks=playbook_cb,
|
|
||||||
runner_callbacks=runner_cb,
|
|
||||||
stats=stats,
|
|
||||||
timeout=options.timeout,
|
|
||||||
transport=options.connection,
|
|
||||||
become=options.become,
|
|
||||||
become_method=options.become_method,
|
|
||||||
become_user=options.become_user,
|
|
||||||
become_pass=becomepass,
|
|
||||||
extra_vars=extra_vars,
|
|
||||||
private_key_file=options.private_key_file,
|
|
||||||
only_tags=only_tags,
|
|
||||||
skip_tags=skip_tags,
|
|
||||||
check=options.check,
|
|
||||||
diff=options.diff,
|
|
||||||
vault_password=vault_pass,
|
|
||||||
force_handlers=options.force_handlers,
|
|
||||||
)
|
|
||||||
|
|
||||||
if options.flush_cache:
|
|
||||||
display(callbacks.banner("FLUSHING FACT CACHE"))
|
|
||||||
pb.SETUP_CACHE.flush()
|
|
||||||
|
|
||||||
if options.listhosts or options.listtasks or options.syntax or options.listtags:
|
|
||||||
print ''
|
|
||||||
print 'playbook: %s' % playbook
|
|
||||||
print ''
|
|
||||||
playnum = 0
|
|
||||||
for (play_ds, play_basedir) in zip(pb.playbook, pb.play_basedirs):
|
|
||||||
playnum += 1
|
|
||||||
play = ansible.playbook.Play(pb, play_ds, play_basedir,
|
|
||||||
vault_password=pb.vault_password)
|
|
||||||
label = play.name
|
|
||||||
hosts = pb.inventory.list_hosts(play.hosts)
|
|
||||||
|
|
||||||
if options.listhosts:
|
|
||||||
print ' play #%d (%s): host count=%d' % (playnum, label, len(hosts))
|
|
||||||
for host in hosts:
|
|
||||||
print ' %s' % host
|
|
||||||
|
|
||||||
if options.listtags or options.listtasks:
|
|
||||||
print ' play #%d (%s):\tTAGS: [%s]' % (playnum, label,','.join(sorted(set(play.tags))))
|
|
||||||
|
|
||||||
if options.listtags:
|
|
||||||
tags = []
|
|
||||||
for task in pb.tasks_to_run_in_play(play):
|
|
||||||
tags.extend(task.tags)
|
|
||||||
print ' TASK TAGS: [%s]' % (', '.join(sorted(set(tags).difference(['untagged']))))
|
|
||||||
|
|
||||||
if options.listtasks:
|
|
||||||
|
|
||||||
for task in pb.tasks_to_run_in_play(play):
|
|
||||||
if getattr(task, 'name', None) is not None:
|
|
||||||
# meta tasks have no names
|
|
||||||
print ' %s\tTAGS: [%s]' % (task.name, ', '.join(sorted(set(task.tags).difference(['untagged']))))
|
|
||||||
|
|
||||||
if options.listhosts or options.listtasks or options.listtags:
|
|
||||||
print ''
|
|
||||||
continue
|
|
||||||
|
|
||||||
if options.syntax:
|
|
||||||
# if we've not exited by now then we are fine.
|
|
||||||
print 'Playbook Syntax is fine'
|
|
||||||
return 0
|
|
||||||
|
|
||||||
failed_hosts = []
|
|
||||||
unreachable_hosts = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
pb.run()
|
|
||||||
|
|
||||||
hosts = sorted(pb.stats.processed.keys())
|
|
||||||
display(callbacks.banner("PLAY RECAP"))
|
|
||||||
playbook_cb.on_stats(pb.stats)
|
|
||||||
|
|
||||||
for h in hosts:
|
|
||||||
t = pb.stats.summarize(h)
|
|
||||||
if t['failures'] > 0:
|
|
||||||
failed_hosts.append(h)
|
|
||||||
if t['unreachable'] > 0:
|
|
||||||
unreachable_hosts.append(h)
|
|
||||||
|
|
||||||
retries = failed_hosts + unreachable_hosts
|
|
||||||
|
|
||||||
if C.RETRY_FILES_ENABLED and len(retries) > 0:
|
|
||||||
filename = pb.generate_retry_inventory(retries)
|
|
||||||
if filename:
|
|
||||||
display(" to retry, use: --limit @%s\n" % filename)
|
|
||||||
|
|
||||||
for h in hosts:
|
|
||||||
t = pb.stats.summarize(h)
|
|
||||||
|
|
||||||
display("%s : %s %s %s %s" % (
|
|
||||||
hostcolor(h, t),
|
|
||||||
colorize('ok', t['ok'], 'green'),
|
|
||||||
colorize('changed', t['changed'], 'yellow'),
|
|
||||||
colorize('unreachable', t['unreachable'], 'red'),
|
|
||||||
colorize('failed', t['failures'], 'red')),
|
|
||||||
screen_only=True
|
|
||||||
)
|
|
||||||
|
|
||||||
display("%s : %s %s %s %s" % (
|
|
||||||
hostcolor(h, t, False),
|
|
||||||
colorize('ok', t['ok'], None),
|
|
||||||
colorize('changed', t['changed'], None),
|
|
||||||
colorize('unreachable', t['unreachable'], None),
|
|
||||||
colorize('failed', t['failures'], None)),
|
|
||||||
log_only=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
print ""
|
|
||||||
if len(failed_hosts) > 0:
|
|
||||||
return 2
|
|
||||||
if len(unreachable_hosts) > 0:
|
|
||||||
return 3
|
|
||||||
|
|
||||||
except errors.AnsibleError, e:
|
|
||||||
display("ERROR: %s" % e, color='red')
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
display(" ", log_only=True)
|
|
||||||
display(" ".join(sys.argv), log_only=True)
|
|
||||||
display(" ", log_only=True)
|
|
||||||
try:
|
|
||||||
sys.exit(main(sys.argv[1:]))
|
|
||||||
except errors.AnsibleError, e:
|
|
||||||
display("ERROR: %s" % e, color='red', stderr=True)
|
|
||||||
sys.exit(1)
|
|
||||||
except KeyboardInterrupt, ke:
|
|
||||||
display("ERROR: interrupted", color='red', stderr=True)
|
|
||||||
sys.exit(1)
|
|
@ -0,0 +1 @@
|
|||||||
|
ansible
|
@ -1,257 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# (c) 2012, Stephen Fromm <sfromm@gmail.com>
|
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
# ansible-pull is a script that runs ansible in local mode
|
|
||||||
# after checking out a playbooks directory from source repo. There is an
|
|
||||||
# example playbook to bootstrap this script in the examples/ dir which
|
|
||||||
# installs ansible and sets it up to run on cron.
|
|
||||||
|
|
||||||
# usage:
|
|
||||||
# ansible-pull -d /var/lib/ansible \
|
|
||||||
# -U http://example.net/content.git [-C production] \
|
|
||||||
# [path/playbook.yml]
|
|
||||||
#
|
|
||||||
# the -d and -U arguments are required; the -C argument is optional.
|
|
||||||
#
|
|
||||||
# ansible-pull accepts an optional argument to specify a playbook
|
|
||||||
# location underneath the workdir and then searches the source repo
|
|
||||||
# for playbooks in the following order, stopping at the first match:
|
|
||||||
#
|
|
||||||
# 1. $workdir/path/playbook.yml, if specified
|
|
||||||
# 2. $workdir/$fqdn.yml
|
|
||||||
# 3. $workdir/$hostname.yml
|
|
||||||
# 4. $workdir/local.yml
|
|
||||||
#
|
|
||||||
# the source repo must contain at least one of these playbooks.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
import datetime
|
|
||||||
import socket
|
|
||||||
import random
|
|
||||||
import time
|
|
||||||
from ansible import utils
|
|
||||||
from ansible.utils import cmd_functions
|
|
||||||
from ansible import errors
|
|
||||||
from ansible import inventory
|
|
||||||
|
|
||||||
DEFAULT_REPO_TYPE = 'git'
|
|
||||||
DEFAULT_PLAYBOOK = 'local.yml'
|
|
||||||
PLAYBOOK_ERRORS = {1: 'File does not exist',
|
|
||||||
2: 'File is not readable'}
|
|
||||||
|
|
||||||
VERBOSITY=0
|
|
||||||
|
|
||||||
def increment_debug(option, opt, value, parser):
|
|
||||||
global VERBOSITY
|
|
||||||
VERBOSITY += 1
|
|
||||||
|
|
||||||
def try_playbook(path):
|
|
||||||
if not os.path.exists(path):
|
|
||||||
return 1
|
|
||||||
if not os.access(path, os.R_OK):
|
|
||||||
return 2
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def select_playbook(path, args):
|
|
||||||
playbook = None
|
|
||||||
if len(args) > 0 and args[0] is not None:
|
|
||||||
playbook = "%s/%s" % (path, args[0])
|
|
||||||
rc = try_playbook(playbook)
|
|
||||||
if rc != 0:
|
|
||||||
print >>sys.stderr, "%s: %s" % (playbook, PLAYBOOK_ERRORS[rc])
|
|
||||||
return None
|
|
||||||
return playbook
|
|
||||||
else:
|
|
||||||
fqdn = socket.getfqdn()
|
|
||||||
hostpb = "%s/%s.yml" % (path, fqdn)
|
|
||||||
shorthostpb = "%s/%s.yml" % (path, fqdn.split('.')[0])
|
|
||||||
localpb = "%s/%s" % (path, DEFAULT_PLAYBOOK)
|
|
||||||
errors = []
|
|
||||||
for pb in [hostpb, shorthostpb, localpb]:
|
|
||||||
rc = try_playbook(pb)
|
|
||||||
if rc == 0:
|
|
||||||
playbook = pb
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
errors.append("%s: %s" % (pb, PLAYBOOK_ERRORS[rc]))
|
|
||||||
if playbook is None:
|
|
||||||
print >>sys.stderr, "\n".join(errors)
|
|
||||||
return playbook
|
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
|
||||||
""" Set up and run a local playbook """
|
|
||||||
usage = "%prog [options] [playbook.yml]"
|
|
||||||
parser = utils.SortedOptParser(usage=usage)
|
|
||||||
parser.add_option('--purge', default=False, action='store_true',
|
|
||||||
help='purge checkout after playbook run')
|
|
||||||
parser.add_option('-o', '--only-if-changed', dest='ifchanged', default=False, action='store_true',
|
|
||||||
help='only run the playbook if the repository has been updated')
|
|
||||||
parser.add_option('-s', '--sleep', dest='sleep', default=None,
|
|
||||||
help='sleep for random interval (between 0 and n number of seconds) before starting. this is a useful way to disperse git requests')
|
|
||||||
parser.add_option('-f', '--force', dest='force', default=False,
|
|
||||||
action='store_true',
|
|
||||||
help='run the playbook even if the repository could '
|
|
||||||
'not be updated')
|
|
||||||
parser.add_option('-d', '--directory', dest='dest', default=None,
|
|
||||||
help='directory to checkout repository to')
|
|
||||||
#parser.add_option('-l', '--live', default=True, action='store_live',
|
|
||||||
# help='Print the ansible-playbook output while running')
|
|
||||||
parser.add_option('-U', '--url', dest='url', default=None,
|
|
||||||
help='URL of the playbook repository')
|
|
||||||
parser.add_option('-C', '--checkout', dest='checkout',
|
|
||||||
help='branch/tag/commit to checkout. '
|
|
||||||
'Defaults to behavior of repository module.')
|
|
||||||
parser.add_option('-i', '--inventory-file', dest='inventory',
|
|
||||||
help="location of the inventory host file")
|
|
||||||
parser.add_option('-e', '--extra-vars', dest="extra_vars", action="append",
|
|
||||||
help="set additional variables as key=value or YAML/JSON", default=[])
|
|
||||||
parser.add_option('-v', '--verbose', default=False, action="callback",
|
|
||||||
callback=increment_debug,
|
|
||||||
help='Pass -vvvv to ansible-playbook')
|
|
||||||
parser.add_option('-m', '--module-name', dest='module_name',
|
|
||||||
default=DEFAULT_REPO_TYPE,
|
|
||||||
help='Module name used to check out repository. '
|
|
||||||
'Default is %s.' % DEFAULT_REPO_TYPE)
|
|
||||||
parser.add_option('--vault-password-file', dest='vault_password_file',
|
|
||||||
help="vault password file")
|
|
||||||
parser.add_option('-K', '--ask-sudo-pass', default=False, dest='ask_sudo_pass', action='store_true',
|
|
||||||
help='ask for sudo password')
|
|
||||||
parser.add_option('-t', '--tags', dest='tags', default=False,
|
|
||||||
help='only run plays and tasks tagged with these values')
|
|
||||||
parser.add_option('--accept-host-key', default=False, dest='accept_host_key', action='store_true',
|
|
||||||
help='adds the hostkey for the repo url if not already added')
|
|
||||||
parser.add_option('--key-file', dest='key_file',
|
|
||||||
help="Pass '-i <key_file>' to the SSH arguments used by git.")
|
|
||||||
options, args = parser.parse_args(args)
|
|
||||||
|
|
||||||
hostname = socket.getfqdn()
|
|
||||||
if not options.dest:
|
|
||||||
# use a hostname dependent directory, in case of $HOME on nfs
|
|
||||||
options.dest = utils.prepare_writeable_dir('~/.ansible/pull/%s' % hostname)
|
|
||||||
|
|
||||||
options.dest = os.path.abspath(options.dest)
|
|
||||||
|
|
||||||
if not options.url:
|
|
||||||
parser.error("URL for repository not specified, use -h for help")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
print now.strftime("Starting ansible-pull at %F %T")
|
|
||||||
|
|
||||||
# Attempt to use the inventory passed in as an argument
|
|
||||||
# It might not yet have been downloaded so use localhost if note
|
|
||||||
if not options.inventory or not os.path.exists(options.inventory):
|
|
||||||
inv_opts = 'localhost,'
|
|
||||||
else:
|
|
||||||
inv_opts = options.inventory
|
|
||||||
limit_opts = 'localhost:%s:127.0.0.1' % hostname
|
|
||||||
repo_opts = "name=%s dest=%s" % (options.url, options.dest)
|
|
||||||
|
|
||||||
if VERBOSITY == 0:
|
|
||||||
base_opts = '-c local --limit "%s"' % limit_opts
|
|
||||||
elif VERBOSITY > 0:
|
|
||||||
debug_level = ''.join([ "v" for x in range(0, VERBOSITY) ])
|
|
||||||
base_opts = '-%s -c local --limit "%s"' % (debug_level, limit_opts)
|
|
||||||
|
|
||||||
if options.checkout:
|
|
||||||
repo_opts += ' version=%s' % options.checkout
|
|
||||||
|
|
||||||
# Only git module is supported
|
|
||||||
if options.module_name == DEFAULT_REPO_TYPE:
|
|
||||||
if options.accept_host_key:
|
|
||||||
repo_opts += ' accept_hostkey=yes'
|
|
||||||
|
|
||||||
if options.key_file:
|
|
||||||
repo_opts += ' key_file=%s' % options.key_file
|
|
||||||
|
|
||||||
path = utils.plugins.module_finder.find_plugin(options.module_name)
|
|
||||||
if path is None:
|
|
||||||
sys.stderr.write("module '%s' not found.\n" % options.module_name)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
bin_path = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
cmd = '%s/ansible localhost -i "%s" %s -m %s -a "%s"' % (
|
|
||||||
bin_path, inv_opts, base_opts, options.module_name, repo_opts
|
|
||||||
)
|
|
||||||
|
|
||||||
for ev in options.extra_vars:
|
|
||||||
cmd += ' -e "%s"' % ev
|
|
||||||
|
|
||||||
if options.sleep:
|
|
||||||
try:
|
|
||||||
secs = random.randint(0,int(options.sleep));
|
|
||||||
except ValueError:
|
|
||||||
parser.error("%s is not a number." % options.sleep)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
print >>sys.stderr, "Sleeping for %d seconds..." % secs
|
|
||||||
time.sleep(secs);
|
|
||||||
|
|
||||||
|
|
||||||
# RUN THe CHECKOUT COMMAND
|
|
||||||
rc, out, err = cmd_functions.run_cmd(cmd, live=True)
|
|
||||||
|
|
||||||
if rc != 0:
|
|
||||||
if options.force:
|
|
||||||
print >>sys.stderr, "Unable to update repository. Continuing with (forced) run of playbook."
|
|
||||||
else:
|
|
||||||
return rc
|
|
||||||
elif options.ifchanged and '"changed": true' not in out:
|
|
||||||
print "Repository has not changed, quitting."
|
|
||||||
return 0
|
|
||||||
|
|
||||||
playbook = select_playbook(options.dest, args)
|
|
||||||
|
|
||||||
if playbook is None:
|
|
||||||
print >>sys.stderr, "Could not find a playbook to run."
|
|
||||||
return 1
|
|
||||||
|
|
||||||
cmd = '%s/ansible-playbook %s %s' % (bin_path, base_opts, playbook)
|
|
||||||
if options.vault_password_file:
|
|
||||||
cmd += " --vault-password-file=%s" % options.vault_password_file
|
|
||||||
if options.inventory:
|
|
||||||
cmd += ' -i "%s"' % options.inventory
|
|
||||||
for ev in options.extra_vars:
|
|
||||||
cmd += ' -e "%s"' % ev
|
|
||||||
if options.ask_sudo_pass:
|
|
||||||
cmd += ' -K'
|
|
||||||
if options.tags:
|
|
||||||
cmd += ' -t "%s"' % options.tags
|
|
||||||
os.chdir(options.dest)
|
|
||||||
|
|
||||||
# RUN THE PLAYBOOK COMMAND
|
|
||||||
rc, out, err = cmd_functions.run_cmd(cmd, live=True)
|
|
||||||
|
|
||||||
if options.purge:
|
|
||||||
os.chdir('/')
|
|
||||||
try:
|
|
||||||
shutil.rmtree(options.dest)
|
|
||||||
except Exception, e:
|
|
||||||
print >>sys.stderr, "Failed to remove %s: %s" % (options.dest, str(e))
|
|
||||||
|
|
||||||
return rc
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
try:
|
|
||||||
sys.exit(main(sys.argv[1:]))
|
|
||||||
except KeyboardInterrupt, e:
|
|
||||||
print >>sys.stderr, "Exit on user request.\n"
|
|
||||||
sys.exit(1)
|
|
@ -0,0 +1 @@
|
|||||||
|
ansible
|
@ -1,241 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# (c) 2014, James Tanner <tanner.jc@gmail.com>
|
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
# ansible-vault is a script that encrypts/decrypts YAML files. See
|
|
||||||
# http://docs.ansible.com/playbooks_vault.html for more details.
|
|
||||||
|
|
||||||
__requires__ = ['ansible']
|
|
||||||
try:
|
|
||||||
import pkg_resources
|
|
||||||
except Exception:
|
|
||||||
# Use pkg_resources to find the correct versions of libraries and set
|
|
||||||
# sys.path appropriately when there are multiversion installs. But we
|
|
||||||
# have code that better expresses the errors in the places where the code
|
|
||||||
# is actually used (the deps are optional for many code paths) so we don't
|
|
||||||
# want to fail here.
|
|
||||||
pass
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
import ansible.constants as C
|
|
||||||
|
|
||||||
from ansible import utils
|
|
||||||
from ansible import errors
|
|
||||||
from ansible.utils.vault import VaultEditor
|
|
||||||
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
# Utility functions for parsing actions/options
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
VALID_ACTIONS = ("create", "decrypt", "edit", "encrypt", "rekey", "view")
|
|
||||||
|
|
||||||
def build_option_parser(action):
|
|
||||||
"""
|
|
||||||
Builds an option parser object based on the action
|
|
||||||
the user wants to execute.
|
|
||||||
"""
|
|
||||||
|
|
||||||
usage = "usage: %%prog [%s] [--help] [options] file_name" % "|".join(VALID_ACTIONS)
|
|
||||||
epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])
|
|
||||||
OptionParser.format_epilog = lambda self, formatter: self.epilog
|
|
||||||
parser = OptionParser(usage=usage, epilog=epilog)
|
|
||||||
|
|
||||||
if not action:
|
|
||||||
parser.print_help()
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
# options for all actions
|
|
||||||
#parser.add_option('-c', '--cipher', dest='cipher', default="AES256", help="cipher to use")
|
|
||||||
parser.add_option('--debug', dest='debug', action="store_true", help="debug")
|
|
||||||
parser.add_option('--vault-password-file', dest='password_file',
|
|
||||||
help="vault password file", default=C.DEFAULT_VAULT_PASSWORD_FILE)
|
|
||||||
|
|
||||||
# options specific to actions
|
|
||||||
if action == "create":
|
|
||||||
parser.set_usage("usage: %prog create [options] file_name")
|
|
||||||
elif action == "decrypt":
|
|
||||||
parser.set_usage("usage: %prog decrypt [options] file_name")
|
|
||||||
elif action == "edit":
|
|
||||||
parser.set_usage("usage: %prog edit [options] file_name")
|
|
||||||
elif action == "view":
|
|
||||||
parser.set_usage("usage: %prog view [options] file_name")
|
|
||||||
elif action == "encrypt":
|
|
||||||
parser.set_usage("usage: %prog encrypt [options] file_name")
|
|
||||||
elif action == "rekey":
|
|
||||||
parser.set_usage("usage: %prog rekey [options] file_name")
|
|
||||||
|
|
||||||
# done, return the parser
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def get_action(args):
|
|
||||||
"""
|
|
||||||
Get the action the user wants to execute from the
|
|
||||||
sys argv list.
|
|
||||||
"""
|
|
||||||
for i in range(0,len(args)):
|
|
||||||
arg = args[i]
|
|
||||||
if arg in VALID_ACTIONS:
|
|
||||||
del args[i]
|
|
||||||
return arg
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_opt(options, k, defval=""):
|
|
||||||
"""
|
|
||||||
Returns an option from an Optparse values instance.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
data = getattr(options, k)
|
|
||||||
except:
|
|
||||||
return defval
|
|
||||||
if k == "roles_path":
|
|
||||||
if os.pathsep in data:
|
|
||||||
data = data.split(os.pathsep)[0]
|
|
||||||
return data
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
# Command functions
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def execute_create(args, options, parser):
|
|
||||||
if len(args) > 1:
|
|
||||||
raise errors.AnsibleError("'create' does not accept more than one filename")
|
|
||||||
|
|
||||||
if not options.password_file:
|
|
||||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True, confirm_vault=True)
|
|
||||||
else:
|
|
||||||
password = utils.read_vault_file(options.password_file)
|
|
||||||
|
|
||||||
cipher = 'AES256'
|
|
||||||
if hasattr(options, 'cipher'):
|
|
||||||
cipher = options.cipher
|
|
||||||
|
|
||||||
this_editor = VaultEditor(cipher, password, args[0])
|
|
||||||
this_editor.create_file()
|
|
||||||
|
|
||||||
def execute_decrypt(args, options, parser):
|
|
||||||
|
|
||||||
if not options.password_file:
|
|
||||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
|
|
||||||
else:
|
|
||||||
password = utils.read_vault_file(options.password_file)
|
|
||||||
|
|
||||||
cipher = 'AES256'
|
|
||||||
if hasattr(options, 'cipher'):
|
|
||||||
cipher = options.cipher
|
|
||||||
|
|
||||||
for f in args:
|
|
||||||
this_editor = VaultEditor(cipher, password, f)
|
|
||||||
this_editor.decrypt_file()
|
|
||||||
|
|
||||||
print "Decryption successful"
|
|
||||||
|
|
||||||
def execute_edit(args, options, parser):
|
|
||||||
|
|
||||||
if len(args) > 1:
|
|
||||||
raise errors.AnsibleError("edit does not accept more than one filename")
|
|
||||||
|
|
||||||
if not options.password_file:
|
|
||||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
|
|
||||||
else:
|
|
||||||
password = utils.read_vault_file(options.password_file)
|
|
||||||
|
|
||||||
cipher = None
|
|
||||||
|
|
||||||
for f in args:
|
|
||||||
this_editor = VaultEditor(cipher, password, f)
|
|
||||||
this_editor.edit_file()
|
|
||||||
|
|
||||||
def execute_view(args, options, parser):
|
|
||||||
|
|
||||||
if len(args) > 1:
|
|
||||||
raise errors.AnsibleError("view does not accept more than one filename")
|
|
||||||
|
|
||||||
if not options.password_file:
|
|
||||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
|
|
||||||
else:
|
|
||||||
password = utils.read_vault_file(options.password_file)
|
|
||||||
|
|
||||||
cipher = None
|
|
||||||
|
|
||||||
for f in args:
|
|
||||||
this_editor = VaultEditor(cipher, password, f)
|
|
||||||
this_editor.view_file()
|
|
||||||
|
|
||||||
def execute_encrypt(args, options, parser):
|
|
||||||
|
|
||||||
if not options.password_file:
|
|
||||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True, confirm_vault=True)
|
|
||||||
else:
|
|
||||||
password = utils.read_vault_file(options.password_file)
|
|
||||||
|
|
||||||
cipher = 'AES256'
|
|
||||||
if hasattr(options, 'cipher'):
|
|
||||||
cipher = options.cipher
|
|
||||||
|
|
||||||
for f in args:
|
|
||||||
this_editor = VaultEditor(cipher, password, f)
|
|
||||||
this_editor.encrypt_file()
|
|
||||||
|
|
||||||
print "Encryption successful"
|
|
||||||
|
|
||||||
def execute_rekey(args, options, parser):
|
|
||||||
|
|
||||||
if not options.password_file:
|
|
||||||
password, __ = utils.ask_vault_passwords(ask_vault_pass=True)
|
|
||||||
else:
|
|
||||||
password = utils.read_vault_file(options.password_file)
|
|
||||||
|
|
||||||
__, new_password = utils.ask_vault_passwords(ask_vault_pass=False, ask_new_vault_pass=True, confirm_new=True)
|
|
||||||
|
|
||||||
cipher = None
|
|
||||||
for f in args:
|
|
||||||
this_editor = VaultEditor(cipher, password, f)
|
|
||||||
this_editor.rekey_file(new_password)
|
|
||||||
|
|
||||||
print "Rekey successful"
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
# MAIN
|
|
||||||
#-------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
action = get_action(sys.argv)
|
|
||||||
parser = build_option_parser(action)
|
|
||||||
(options, args) = parser.parse_args()
|
|
||||||
|
|
||||||
if not len(args):
|
|
||||||
raise errors.AnsibleError(
|
|
||||||
"The '%s' command requires a filename as the first argument" % action
|
|
||||||
)
|
|
||||||
|
|
||||||
# execute the desired action
|
|
||||||
try:
|
|
||||||
fn = globals()["execute_%s" % action]
|
|
||||||
fn(args, options, parser)
|
|
||||||
except Exception, err:
|
|
||||||
if options.debug:
|
|
||||||
print traceback.format_exc()
|
|
||||||
print "ERROR:",err
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -0,0 +1 @@
|
|||||||
|
ansible
|
@ -0,0 +1,17 @@
|
|||||||
|
inventory
|
||||||
|
=========
|
||||||
|
|
||||||
|
Inventory scripts allow you to store your hosts, groups, and variables in any way
|
||||||
|
you like. Examples include discovering inventory from EC2 or pulling it from
|
||||||
|
Cobbler. These could also be used to interface with LDAP or database.
|
||||||
|
|
||||||
|
chmod +x an inventory plugin and either name it /etc/ansible/hosts or use ansible
|
||||||
|
with -i to designate the path to the script. You might also need to copy a configuration
|
||||||
|
file with the same name and/or set environment variables, the scripts or configuration
|
||||||
|
files have more details.
|
||||||
|
|
||||||
|
contributions welcome
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Send in pull requests to add plugins of your own. The sky is the limit!
|
||||||
|
|
@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# (c) 2015, Marc Abramowitz <marca@surveymonkey.com>
|
||||||
|
#
|
||||||
|
# This file is part of Ansible.
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Dynamic inventory script which lets you use nodes discovered by Canonical's
|
||||||
|
# Landscape (http://www.ubuntu.com/management/landscape-features).
|
||||||
|
#
|
||||||
|
# Requires the `landscape_api` Python module
|
||||||
|
# See:
|
||||||
|
# - https://landscape.canonical.com/static/doc/api/api-client-package.html
|
||||||
|
# - https://landscape.canonical.com/static/doc/api/python-api.html
|
||||||
|
#
|
||||||
|
# Environment variables
|
||||||
|
# ---------------------
|
||||||
|
# - `LANDSCAPE_API_URI`
|
||||||
|
# - `LANDSCAPE_API_KEY`
|
||||||
|
# - `LANDSCAPE_API_SECRET`
|
||||||
|
# - `LANDSCAPE_API_SSL_CA_FILE` (optional)
|
||||||
|
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import collections
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from landscape_api.base import API, HTTPError
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
_key = 'landscape'
|
||||||
|
|
||||||
|
|
||||||
|
class EnvironmentConfig(object):
|
||||||
|
uri = os.getenv('LANDSCAPE_API_URI')
|
||||||
|
access_key = os.getenv('LANDSCAPE_API_KEY')
|
||||||
|
secret_key = os.getenv('LANDSCAPE_API_SECRET')
|
||||||
|
ssl_ca_file = os.getenv('LANDSCAPE_API_SSL_CA_FILE')
|
||||||
|
|
||||||
|
|
||||||
|
def _landscape_client():
|
||||||
|
env = EnvironmentConfig()
|
||||||
|
return API(
|
||||||
|
uri=env.uri,
|
||||||
|
access_key=env.access_key,
|
||||||
|
secret_key=env.secret_key,
|
||||||
|
ssl_ca_file=env.ssl_ca_file)
|
||||||
|
|
||||||
|
|
||||||
|
def get_landscape_members_data():
|
||||||
|
return _landscape_client().get_computers()
|
||||||
|
|
||||||
|
|
||||||
|
def get_nodes(data):
|
||||||
|
return [node['hostname'] for node in data]
|
||||||
|
|
||||||
|
|
||||||
|
def get_groups(data):
|
||||||
|
groups = collections.defaultdict(list)
|
||||||
|
|
||||||
|
for node in data:
|
||||||
|
for value in node['tags']:
|
||||||
|
groups[value].append(node['hostname'])
|
||||||
|
|
||||||
|
return groups
|
||||||
|
|
||||||
|
|
||||||
|
def get_meta(data):
|
||||||
|
meta = {'hostvars': {}}
|
||||||
|
for node in data:
|
||||||
|
meta['hostvars'][node['hostname']] = {'tags': node['tags']}
|
||||||
|
return meta
|
||||||
|
|
||||||
|
|
||||||
|
def print_list():
|
||||||
|
data = get_landscape_members_data()
|
||||||
|
nodes = get_nodes(data)
|
||||||
|
groups = get_groups(data)
|
||||||
|
meta = get_meta(data)
|
||||||
|
inventory_data = {_key: nodes, '_meta': meta}
|
||||||
|
inventory_data.update(groups)
|
||||||
|
print(json.dumps(inventory_data))
|
||||||
|
|
||||||
|
|
||||||
|
def print_host(host):
|
||||||
|
data = get_landscape_members_data()
|
||||||
|
meta = get_meta(data)
|
||||||
|
print(json.dumps(meta['hostvars'][host]))
|
||||||
|
|
||||||
|
|
||||||
|
def get_args(args_list):
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='ansible inventory script reading from landscape cluster')
|
||||||
|
mutex_group = parser.add_mutually_exclusive_group(required=True)
|
||||||
|
help_list = 'list all hosts from landscape cluster'
|
||||||
|
mutex_group.add_argument('--list', action='store_true', help=help_list)
|
||||||
|
help_host = 'display variables for a host'
|
||||||
|
mutex_group.add_argument('--host', help=help_host)
|
||||||
|
return parser.parse_args(args_list)
|
||||||
|
|
||||||
|
|
||||||
|
def main(args_list):
|
||||||
|
args = get_args(args_list)
|
||||||
|
if args.list:
|
||||||
|
print_list()
|
||||||
|
if args.host:
|
||||||
|
print_host(args.host)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# openvz.py
|
||||||
|
#
|
||||||
|
# Copyright 2014 jordonr <jordon@beamsyn.net>
|
||||||
|
#
|
||||||
|
# This file is part of Ansible.
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# Inspired by libvirt_lxc.py inventory script
|
||||||
|
# https://github.com/ansible/ansible/blob/e5ef0eca03cbb6c8950c06dc50d0ca22aa8902f4/plugins/inventory/libvirt_lxc.py
|
||||||
|
#
|
||||||
|
# Groups are determined by the description field of openvz guests
|
||||||
|
# multiple groups can be seperated by commas: webserver,dbserver
|
||||||
|
|
||||||
|
from subprocess import Popen,PIPE
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
#List openvz hosts
|
||||||
|
vzhosts = ['vzhost1','vzhost2','vzhost3']
|
||||||
|
#Add openvz hosts to the inventory and Add "_meta" trick
|
||||||
|
inventory = {'vzhosts': {'hosts': vzhosts}, '_meta': {'hostvars': {}}}
|
||||||
|
#default group, when description not defined
|
||||||
|
default_group = ['vzguest']
|
||||||
|
|
||||||
|
def get_guests():
|
||||||
|
#Loop through vzhosts
|
||||||
|
for h in vzhosts:
|
||||||
|
#SSH to vzhost and get the list of guests in json
|
||||||
|
pipe = Popen(['ssh', h,'vzlist','-j'], stdout=PIPE, universal_newlines=True)
|
||||||
|
|
||||||
|
#Load Json info of guests
|
||||||
|
json_data = json.loads(pipe.stdout.read())
|
||||||
|
|
||||||
|
#loop through guests
|
||||||
|
for j in json_data:
|
||||||
|
#Add information to host vars
|
||||||
|
inventory['_meta']['hostvars'][j['hostname']] = {'ctid': j['ctid'], 'veid': j['veid'], 'vpsid': j['vpsid'], 'private_path': j['private'], 'root_path': j['root'], 'ip': j['ip']}
|
||||||
|
|
||||||
|
#determine group from guest description
|
||||||
|
if j['description'] is not None:
|
||||||
|
groups = j['description'].split(",")
|
||||||
|
else:
|
||||||
|
groups = default_group
|
||||||
|
|
||||||
|
#add guest to inventory
|
||||||
|
for g in groups:
|
||||||
|
if g not in inventory:
|
||||||
|
inventory[g] = {'hosts': []}
|
||||||
|
|
||||||
|
inventory[g]['hosts'].append(j['hostname'])
|
||||||
|
|
||||||
|
return inventory
|
||||||
|
|
||||||
|
|
||||||
|
if len(sys.argv) == 2 and sys.argv[1] == '--list':
|
||||||
|
inv_json = get_guests()
|
||||||
|
print json.dumps(inv_json, sort_keys=True)
|
||||||
|
elif len(sys.argv) == 3 and sys.argv[1] == '--host':
|
||||||
|
print json.dumps({});
|
||||||
|
else:
|
||||||
|
print "Need an argument, either --list or --host <host>"
|
@ -0,0 +1,33 @@
|
|||||||
|
# Copyright 2013 Google Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
# Author: Josha Inglis <jinglis@iix.net> based on the gce.ini by Eric Johnson <erjohnso@google.com>
|
||||||
|
|
||||||
|
[ovirt]
|
||||||
|
# ovirt Service Account configuration information can be stored in the
|
||||||
|
# libcloud 'secrets.py' file. Ideally, the 'secrets.py' file will already
|
||||||
|
# exist in your PYTHONPATH and be picked up automatically with an import
|
||||||
|
# statement in the inventory script. However, you can specify an absolute
|
||||||
|
# path to the secrets.py file with 'libcloud_secrets' parameter.
|
||||||
|
ovirt_api_secrets =
|
||||||
|
|
||||||
|
# If you are not going to use a 'secrets.py' file, you can set the necessary
|
||||||
|
# authorization parameters here.
|
||||||
|
ovirt_url =
|
||||||
|
ovirt_username =
|
||||||
|
ovirt_password =
|
@ -0,0 +1,287 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# Copyright 2015 IIX Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
ovirt external inventory script
|
||||||
|
=================================
|
||||||
|
|
||||||
|
Generates inventory that Ansible can understand by making API requests to
|
||||||
|
oVirt via the ovirt-engine-sdk-python library.
|
||||||
|
|
||||||
|
When run against a specific host, this script returns the following variables
|
||||||
|
based on the data obtained from the ovirt_sdk Node object:
|
||||||
|
- ovirt_uuid
|
||||||
|
- ovirt_id
|
||||||
|
- ovirt_image
|
||||||
|
- ovirt_machine_type
|
||||||
|
- ovirt_ips
|
||||||
|
- ovirt_name
|
||||||
|
- ovirt_description
|
||||||
|
- ovirt_status
|
||||||
|
- ovirt_zone
|
||||||
|
- ovirt_tags
|
||||||
|
- ovirt_stats
|
||||||
|
|
||||||
|
When run in --list mode, instances are grouped by the following categories:
|
||||||
|
|
||||||
|
- zone:
|
||||||
|
zone group name.
|
||||||
|
- instance tags:
|
||||||
|
An entry is created for each tag. For example, if you have two instances
|
||||||
|
with a common tag called 'foo', they will both be grouped together under
|
||||||
|
the 'tag_foo' name.
|
||||||
|
- network name:
|
||||||
|
the name of the network is appended to 'network_' (e.g. the 'default'
|
||||||
|
network will result in a group named 'network_default')
|
||||||
|
- running status:
|
||||||
|
group name prefixed with 'status_' (e.g. status_up, status_down,..)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Execute uname on all instances in the us-central1-a zone
|
||||||
|
$ ansible -i ovirt.py us-central1-a -m shell -a "/bin/uname -a"
|
||||||
|
|
||||||
|
Use the ovirt inventory script to print out instance specific information
|
||||||
|
$ contrib/inventory/ovirt.py --host my_instance
|
||||||
|
|
||||||
|
Author: Josha Inglis <jinglis@iix.net> based on the gce.py by Eric Johnson <erjohnso@google.com>
|
||||||
|
Version: 0.0.1
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_AGENT_PRODUCT = "Ansible-ovirt_inventory_plugin"
|
||||||
|
USER_AGENT_VERSION = "v1"
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import ConfigParser
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
try:
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
from ovirtsdk.api import API
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
from ovirtsdk.xml import params
|
||||||
|
except ImportError:
|
||||||
|
print("ovirt inventory script requires ovirt-engine-sdk-python")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
class OVirtInventory(object):
|
||||||
|
def __init__(self):
|
||||||
|
# Read settings and parse CLI arguments
|
||||||
|
self.args = self.parse_cli_args()
|
||||||
|
self.driver = self.get_ovirt_driver()
|
||||||
|
|
||||||
|
# Just display data for specific host
|
||||||
|
if self.args.host:
|
||||||
|
print self.json_format_dict(
|
||||||
|
self.node_to_dict(self.get_instance(self.args.host)),
|
||||||
|
pretty=self.args.pretty
|
||||||
|
)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Otherwise, assume user wants all instances grouped
|
||||||
|
print(
|
||||||
|
self.json_format_dict(
|
||||||
|
data=self.group_instances(),
|
||||||
|
pretty=self.args.pretty
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_ovirt_driver():
|
||||||
|
"""
|
||||||
|
Determine the ovirt authorization settings and return a ovirt_sdk driver.
|
||||||
|
|
||||||
|
:rtype : ovirtsdk.api.API
|
||||||
|
"""
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
ovirt_ini_default_path = os.path.join(
|
||||||
|
os.path.dirname(os.path.realpath(__file__)), "ovirt.ini")
|
||||||
|
ovirt_ini_path = os.environ.get('OVIRT_INI_PATH', ovirt_ini_default_path)
|
||||||
|
|
||||||
|
# Create a ConfigParser.
|
||||||
|
# This provides empty defaults to each key, so that environment
|
||||||
|
# variable configuration (as opposed to INI configuration) is able
|
||||||
|
# to work.
|
||||||
|
config = ConfigParser.SafeConfigParser(defaults={
|
||||||
|
'ovirt_url': '',
|
||||||
|
'ovirt_username': '',
|
||||||
|
'ovirt_password': '',
|
||||||
|
'ovirt_api_secrets': '',
|
||||||
|
})
|
||||||
|
if 'ovirt' not in config.sections():
|
||||||
|
config.add_section('ovirt')
|
||||||
|
config.read(ovirt_ini_path)
|
||||||
|
|
||||||
|
# Attempt to get ovirt params from a configuration file, if one
|
||||||
|
# exists.
|
||||||
|
secrets_path = config.get('ovirt', 'ovirt_api_secrets')
|
||||||
|
secrets_found = False
|
||||||
|
try:
|
||||||
|
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
kwargs = getattr(secrets, 'OVIRT_KEYWORD_PARAMS', {})
|
||||||
|
secrets_found = True
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not secrets_found and secrets_path:
|
||||||
|
if not secrets_path.endswith('secrets.py'):
|
||||||
|
err = "Must specify ovirt_sdk secrets file as /absolute/path/to/secrets.py"
|
||||||
|
print(err)
|
||||||
|
sys.exit(1)
|
||||||
|
sys.path.append(os.path.dirname(secrets_path))
|
||||||
|
try:
|
||||||
|
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
kwargs = getattr(secrets, 'OVIRT_KEYWORD_PARAMS', {})
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
if not secrets_found:
|
||||||
|
kwargs = {
|
||||||
|
'url': config.get('ovirt', 'ovirt_url'),
|
||||||
|
'username': config.get('ovirt', 'ovirt_username'),
|
||||||
|
'password': config.get('ovirt', 'ovirt_password'),
|
||||||
|
}
|
||||||
|
|
||||||
|
# If the appropriate environment variables are set, they override
|
||||||
|
# other configuration; process those into our args and kwargs.
|
||||||
|
kwargs['url'] = os.environ.get('OVIRT_URL')
|
||||||
|
kwargs['username'] = os.environ.get('OVIRT_EMAIL')
|
||||||
|
kwargs['password'] = os.environ.get('OVIRT_PASS')
|
||||||
|
|
||||||
|
# Retrieve and return the ovirt driver.
|
||||||
|
return API(insecure=True, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_cli_args():
|
||||||
|
"""
|
||||||
|
Command line argument processing
|
||||||
|
|
||||||
|
:rtype : argparse.Namespace
|
||||||
|
"""
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on ovirt')
|
||||||
|
parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)')
|
||||||
|
parser.add_argument('--host', action='store', help='Get all information about an instance')
|
||||||
|
parser.add_argument('--pretty', action='store_true', default=False, help='Pretty format (default: False)')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
def node_to_dict(self, inst):
|
||||||
|
"""
|
||||||
|
:type inst: params.VM
|
||||||
|
"""
|
||||||
|
if inst is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
inst.get_custom_properties()
|
||||||
|
ips = [ip.get_address() for ip in inst.get_guest_info().get_ips().get_ip()] \
|
||||||
|
if inst.get_guest_info() is not None else []
|
||||||
|
stats = {}
|
||||||
|
for stat in inst.get_statistics().list():
|
||||||
|
stats[stat.get_name()] = stat.get_values().get_value()[0].get_datum()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'ovirt_uuid': inst.get_id(),
|
||||||
|
'ovirt_id': inst.get_id(),
|
||||||
|
'ovirt_image': inst.get_os().get_type(),
|
||||||
|
'ovirt_machine_type': inst.get_instance_type(),
|
||||||
|
'ovirt_ips': ips,
|
||||||
|
'ovirt_name': inst.get_name(),
|
||||||
|
'ovirt_description': inst.get_description(),
|
||||||
|
'ovirt_status': inst.get_status().get_state(),
|
||||||
|
'ovirt_zone': inst.get_cluster().get_id(),
|
||||||
|
'ovirt_tags': self.get_tags(inst),
|
||||||
|
'ovirt_stats': stats,
|
||||||
|
# Hosts don't have a public name, so we add an IP
|
||||||
|
'ansible_ssh_host': ips[0] if len(ips) > 0 else None
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_tags(inst):
|
||||||
|
"""
|
||||||
|
:type inst: params.VM
|
||||||
|
"""
|
||||||
|
return [x.get_name() for x in inst.get_tags().list()]
|
||||||
|
|
||||||
|
# noinspection PyBroadException,PyUnusedLocal
|
||||||
|
def get_instance(self, instance_name):
|
||||||
|
"""Gets details about a specific instance """
|
||||||
|
try:
|
||||||
|
return self.driver.vms.get(name=instance_name)
|
||||||
|
except Exception as e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def group_instances(self):
|
||||||
|
"""Group all instances"""
|
||||||
|
groups = defaultdict(list)
|
||||||
|
meta = {"hostvars": {}}
|
||||||
|
|
||||||
|
for node in self.driver.vms.list():
|
||||||
|
assert isinstance(node, params.VM)
|
||||||
|
name = node.get_name()
|
||||||
|
|
||||||
|
meta["hostvars"][name] = self.node_to_dict(node)
|
||||||
|
|
||||||
|
zone = node.get_cluster().get_name()
|
||||||
|
groups[zone].append(name)
|
||||||
|
|
||||||
|
tags = self.get_tags(node)
|
||||||
|
for t in tags:
|
||||||
|
tag = 'tag_%s' % t
|
||||||
|
groups[tag].append(name)
|
||||||
|
|
||||||
|
nets = [x.get_name() for x in node.get_nics().list()]
|
||||||
|
for net in nets:
|
||||||
|
net = 'network_%s' % net
|
||||||
|
groups[net].append(name)
|
||||||
|
|
||||||
|
status = node.get_status().get_state()
|
||||||
|
stat = 'status_%s' % status.lower()
|
||||||
|
if stat in groups:
|
||||||
|
groups[stat].append(name)
|
||||||
|
else:
|
||||||
|
groups[stat] = [name]
|
||||||
|
|
||||||
|
groups["_meta"] = meta
|
||||||
|
|
||||||
|
return groups
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def json_format_dict(data, pretty=False):
|
||||||
|
""" Converts a dict to a JSON object and dumps it as a formatted
|
||||||
|
string """
|
||||||
|
|
||||||
|
if pretty:
|
||||||
|
return json.dumps(data, sort_keys=True, indent=2)
|
||||||
|
else:
|
||||||
|
return json.dumps(data)
|
||||||
|
|
||||||
|
# Run the script
|
||||||
|
OVirtInventory()
|
@ -0,0 +1,178 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright (C) 2014 Mathieu GAUTHIER-LAFAYE <gauthierl@lapth.cnrs.fr>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
class ProxmoxNodeList(list):
|
||||||
|
def get_names(self):
|
||||||
|
return [node['node'] for node in self]
|
||||||
|
|
||||||
|
class ProxmoxQemu(dict):
|
||||||
|
def get_variables(self):
|
||||||
|
variables = {}
|
||||||
|
for key, value in self.iteritems():
|
||||||
|
variables['proxmox_' + key] = value
|
||||||
|
return variables
|
||||||
|
|
||||||
|
class ProxmoxQemuList(list):
|
||||||
|
def __init__(self, data=[]):
|
||||||
|
for item in data:
|
||||||
|
self.append(ProxmoxQemu(item))
|
||||||
|
|
||||||
|
def get_names(self):
|
||||||
|
return [qemu['name'] for qemu in self if qemu['template'] != 1]
|
||||||
|
|
||||||
|
def get_by_name(self, name):
|
||||||
|
results = [qemu for qemu in self if qemu['name'] == name]
|
||||||
|
return results[0] if len(results) > 0 else None
|
||||||
|
|
||||||
|
def get_variables(self):
|
||||||
|
variables = {}
|
||||||
|
for qemu in self:
|
||||||
|
variables[qemu['name']] = qemu.get_variables()
|
||||||
|
|
||||||
|
return variables
|
||||||
|
|
||||||
|
class ProxmoxPoolList(list):
|
||||||
|
def get_names(self):
|
||||||
|
return [pool['poolid'] for pool in self]
|
||||||
|
|
||||||
|
class ProxmoxPool(dict):
|
||||||
|
def get_members_name(self):
|
||||||
|
return [member['name'] for member in self['members'] if member['template'] != 1]
|
||||||
|
|
||||||
|
class ProxmoxAPI(object):
|
||||||
|
def __init__(self, options):
|
||||||
|
self.options = options
|
||||||
|
self.credentials = None
|
||||||
|
|
||||||
|
if not options.url:
|
||||||
|
raise Exception('Missing mandatory parameter --url (or PROXMOX_URL).')
|
||||||
|
elif not options.username:
|
||||||
|
raise Exception('Missing mandatory parameter --username (or PROXMOX_USERNAME).')
|
||||||
|
elif not options.password:
|
||||||
|
raise Exception('Missing mandatory parameter --password (or PROXMOX_PASSWORD).')
|
||||||
|
|
||||||
|
def auth(self):
|
||||||
|
request_path = '{}api2/json/access/ticket'.format(self.options.url)
|
||||||
|
|
||||||
|
request_params = urllib.urlencode({
|
||||||
|
'username': self.options.username,
|
||||||
|
'password': self.options.password,
|
||||||
|
})
|
||||||
|
|
||||||
|
data = json.load(urllib2.urlopen(request_path, request_params))
|
||||||
|
|
||||||
|
self.credentials = {
|
||||||
|
'ticket': data['data']['ticket'],
|
||||||
|
'CSRFPreventionToken': data['data']['CSRFPreventionToken'],
|
||||||
|
}
|
||||||
|
|
||||||
|
def get(self, url, data=None):
|
||||||
|
opener = urllib2.build_opener()
|
||||||
|
opener.addheaders.append(('Cookie', 'PVEAuthCookie={}'.format(self.credentials['ticket'])))
|
||||||
|
|
||||||
|
request_path = '{}{}'.format(self.options.url, url)
|
||||||
|
request = opener.open(request_path, data)
|
||||||
|
|
||||||
|
response = json.load(request)
|
||||||
|
return response['data']
|
||||||
|
|
||||||
|
def nodes(self):
|
||||||
|
return ProxmoxNodeList(self.get('api2/json/nodes'))
|
||||||
|
|
||||||
|
def node_qemu(self, node):
|
||||||
|
return ProxmoxQemuList(self.get('api2/json/nodes/{}/qemu'.format(node)))
|
||||||
|
|
||||||
|
def pools(self):
|
||||||
|
return ProxmoxPoolList(self.get('api2/json/pools'))
|
||||||
|
|
||||||
|
def pool(self, poolid):
|
||||||
|
return ProxmoxPool(self.get('api2/json/pools/{}'.format(poolid)))
|
||||||
|
|
||||||
|
def main_list(options):
|
||||||
|
results = {
|
||||||
|
'all': {
|
||||||
|
'hosts': [],
|
||||||
|
},
|
||||||
|
'_meta': {
|
||||||
|
'hostvars': {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proxmox_api = ProxmoxAPI(options)
|
||||||
|
proxmox_api.auth()
|
||||||
|
|
||||||
|
for node in proxmox_api.nodes().get_names():
|
||||||
|
qemu_list = proxmox_api.node_qemu(node)
|
||||||
|
results['all']['hosts'] += qemu_list.get_names()
|
||||||
|
results['_meta']['hostvars'].update(qemu_list.get_variables())
|
||||||
|
|
||||||
|
# pools
|
||||||
|
for pool in proxmox_api.pools().get_names():
|
||||||
|
results[pool] = {
|
||||||
|
'hosts': proxmox_api.pool(pool).get_members_name(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def main_host(options):
|
||||||
|
proxmox_api = ProxmoxAPI(options)
|
||||||
|
proxmox_api.auth()
|
||||||
|
|
||||||
|
for node in proxmox_api.nodes().get_names():
|
||||||
|
qemu_list = proxmox_api.node_qemu(node)
|
||||||
|
qemu = qemu_list.get_by_name(options.host)
|
||||||
|
if qemu:
|
||||||
|
return qemu.get_variables()
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = OptionParser(usage='%prog [options] --list | --host HOSTNAME')
|
||||||
|
parser.add_option('--list', action="store_true", default=False, dest="list")
|
||||||
|
parser.add_option('--host', dest="host")
|
||||||
|
parser.add_option('--url', default=os.environ.get('PROXMOX_URL'), dest='url')
|
||||||
|
parser.add_option('--username', default=os.environ.get('PROXMOX_USERNAME'), dest='username')
|
||||||
|
parser.add_option('--password', default=os.environ.get('PROXMOX_PASSWORD'), dest='password')
|
||||||
|
parser.add_option('--pretty', action="store_true", default=False, dest='pretty')
|
||||||
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
|
if options.list:
|
||||||
|
data = main_list(options)
|
||||||
|
elif options.host:
|
||||||
|
data = main_host(options)
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
indent = None
|
||||||
|
if options.pretty:
|
||||||
|
indent = 2
|
||||||
|
|
||||||
|
print json.dumps(data, indent=indent)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# (c) 2015, Marc Abramowitz <marca@surveymonkey.com>
|
||||||
|
#
|
||||||
|
# This file is part of Ansible.
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Dynamic inventory script which lets you use nodes discovered by Serf
|
||||||
|
# (https://serfdom.io/).
|
||||||
|
#
|
||||||
|
# Requires the `serfclient` Python module from
|
||||||
|
# https://pypi.python.org/pypi/serfclient
|
||||||
|
#
|
||||||
|
# Environment variables
|
||||||
|
# ---------------------
|
||||||
|
# - `SERF_RPC_ADDR`
|
||||||
|
# - `SERF_RPC_AUTH`
|
||||||
|
#
|
||||||
|
# These variables are described at https://www.serfdom.io/docs/commands/members.html#_rpc_addr
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import collections
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# https://pypi.python.org/pypi/serfclient
|
||||||
|
from serfclient import SerfClient, EnvironmentConfig
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
_key = 'serf'
|
||||||
|
|
||||||
|
|
||||||
|
def _serf_client():
|
||||||
|
env = EnvironmentConfig()
|
||||||
|
return SerfClient(host=env.host, port=env.port, rpc_auth=env.auth_key)
|
||||||
|
|
||||||
|
|
||||||
|
def get_serf_members_data():
|
||||||
|
return _serf_client().members().body['Members']
|
||||||
|
|
||||||
|
|
||||||
|
def get_nodes(data):
|
||||||
|
return [node['Name'] for node in data]
|
||||||
|
|
||||||
|
|
||||||
|
def get_groups(data):
|
||||||
|
groups = collections.defaultdict(list)
|
||||||
|
|
||||||
|
for node in data:
|
||||||
|
for key, value in node['Tags'].items():
|
||||||
|
groups[value].append(node['Name'])
|
||||||
|
|
||||||
|
return groups
|
||||||
|
|
||||||
|
|
||||||
|
def get_meta(data):
|
||||||
|
meta = {'hostvars': {}}
|
||||||
|
for node in data:
|
||||||
|
meta['hostvars'][node['Name']] = node['Tags']
|
||||||
|
return meta
|
||||||
|
|
||||||
|
|
||||||
|
def print_list():
|
||||||
|
data = get_serf_members_data()
|
||||||
|
nodes = get_nodes(data)
|
||||||
|
groups = get_groups(data)
|
||||||
|
meta = get_meta(data)
|
||||||
|
inventory_data = {_key: nodes, '_meta': meta}
|
||||||
|
inventory_data.update(groups)
|
||||||
|
print(json.dumps(inventory_data))
|
||||||
|
|
||||||
|
|
||||||
|
def print_host(host):
|
||||||
|
data = get_serf_members_data()
|
||||||
|
meta = get_meta(data)
|
||||||
|
print(json.dumps(meta['hostvars'][host]))
|
||||||
|
|
||||||
|
|
||||||
|
def get_args(args_list):
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='ansible inventory script reading from serf cluster')
|
||||||
|
mutex_group = parser.add_mutually_exclusive_group(required=True)
|
||||||
|
help_list = 'list all hosts from serf cluster'
|
||||||
|
mutex_group.add_argument('--list', action='store_true', help=help_list)
|
||||||
|
help_host = 'display variables for a host'
|
||||||
|
mutex_group.add_argument('--host', help=help_host)
|
||||||
|
return parser.parse_args(args_list)
|
||||||
|
|
||||||
|
|
||||||
|
def main(args_list):
|
||||||
|
args = get_args(args_list)
|
||||||
|
if args.list:
|
||||||
|
print_list()
|
||||||
|
if args.host:
|
||||||
|
print_host(args.host)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
@ -0,0 +1,13 @@
|
|||||||
|
Core Modules
|
||||||
|
------------
|
||||||
|
|
||||||
|
These are modules that the core ansible team maintains and will always ship with ansible itself.
|
||||||
|
They will also receive slightly higher priority for all requests than those in the "extras" repos.
|
||||||
|
|
||||||
|
The source of these modules is hosted on GitHub in the `ansible-modules-core <http://github.com/ansible/ansible-modules-core>`_ repo.
|
||||||
|
|
||||||
|
If you believe you have found a bug in a core module and are already running the latest stable or development version of Ansible, first look in the `issue tracker at github.com/ansible/ansible-modules-core <http://github.com/ansible/ansible-modules-core>`_ to see if a bug has already been filed. If not, we would be grateful if you would file one.
|
||||||
|
|
||||||
|
Should you have a question rather than a bug report, inquries are welcome on the `ansible-project google group <https://groups.google.com/forum/#!forum/ansible-project>`_ or on Ansible's "#ansible" channel, located on irc.freenode.net. Development oriented topics should instead use the similar `ansible-devel google group <https://groups.google.com/forum/#!forum/ansible-devel>`_.
|
||||||
|
|
||||||
|
Documentation updates for these modules can also be edited directly in the module itself and by submitting a pull request to the module source code, just look for the "DOCUMENTATION" block in the source tree.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue