2013-12-19 02:15:37 +01:00
#!/usr/bin/env python
########################################################################
#
2014-01-28 17:38:52 +01:00
# (C) 2013, James Cammarata <jcammarata@ansible.com>
2013-12-19 02:15:37 +01:00
#
# 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
2014-08-13 13:32:36 +02:00
import subprocess
2013-12-19 02:15:37 +01:00
import sys
import tarfile
import tempfile
2013-12-20 15:55:53 +01:00
import urllib
2013-12-19 02:15:37 +01:00
import yaml
from collections import defaultdict
from distutils.version import LooseVersion
from jinja2 import Environment
from optparse import OptionParser
2013-12-19 04:40:58 +01:00
import ansible.constants as C
2014-08-14 12:20:10 +02:00
import ansible.utils
2014-08-21 22:56:58 +02:00
from ansible.errors import AnsibleError
2015-09-17 06:14:32 +02:00
from ansible.module_utils.urls import open_url
2013-12-19 04:40:58 +01:00
2013-12-19 02:15:37 +01:00
default_meta_template = """---
galaxy_info:
author: {{ author }}
description: {{description}}
company: {{ company }}
2015-02-10 14:44:45 +01:00
# 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 }}
2014-01-24 22:34:01 +01:00
# Some suggested licenses:
# - BSD (default)
# - MIT
# - GPLv2
# - GPLv3
# - Apache
# - CC-BY
2013-12-19 02:15:37 +01:00
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 %}
2013-12-19 20:20:42 +01:00
dependencies: []
2014-09-24 23:47:12 +02:00
# List your role dependencies here, one per line.
2013-12-19 20:20:42 +01:00
# Be sure to remove the '[]' above if you add dependencies
# to this list.
2013-12-19 02:15:37 +01:00
{% for dependency in dependencies %}
#- {{ dependency }}
{% endfor %}
"""
2013-12-20 17:29:08 +01:00
default_readme_template = """Role Name
2014-07-17 19:29:58 +02:00
=========
2013-12-20 17:29:08 +01:00
A brief description of the role goes here.
Requirements
------------
2013-12-25 00:05:39 +01:00
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.
2013-12-20 17:29:08 +01:00
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.
2014-01-24 22:39:30 +01:00
Example Playbook
2014-07-17 19:29:58 +02:00
----------------
2014-01-24 22:39:30 +01:00
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 }
2013-12-20 17:29:08 +01:00
License
-------
2013-12-20 19:14:14 +01:00
BSD
2013-12-20 17:29:08 +01:00
Author Information
------------------
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
"""
2013-12-19 02:15:37 +01:00
#-------------------------------------------------------------------------------------
# Utility functions for parsing actions/options
#-------------------------------------------------------------------------------------
VALID_ACTIONS = ("init", "info", "install", "list", "remove")
2014-11-12 22:23:49 +01:00
SKIP_INFO_KEYS = ("platforms","readme_html", "related", "summary_fields", "average_aw_composite", "average_aw_score", "url" )
2013-12-19 02:15:37 +01:00
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.
"""
2013-12-20 22:08:14 +01:00
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)
2013-12-19 02:15:37 +01:00
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="./",
2014-03-17 19:52:44 +01:00
help='The path in which the skeleton role will be created. '
2013-12-19 02:15:37 +01:00
'The default is the current working directory.')
2014-08-19 11:54:48 +02:00
parser.add_option(
'--offline', dest='offline', default=False, action='store_true',
help="Don't query the galaxy API when creating roles")
2013-12-19 02:15:37 +01:00
elif action == "install":
2014-08-14 12:20:10 +02:00
parser.set_usage("usage: %prog install [options] [-r FILE | role_name(s)[,version] | scm+role_repo_url[,version] | tar_file(s)]")
2013-12-19 02:15:37 +01:00
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(
2014-03-17 19:52:44 +01:00
'-r', '--role-file', dest='role_file',
2013-12-19 02:15:37 +01:00
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(
2013-12-19 04:40:58 +01:00
'-p', '--roles-path', dest='roles_path', default=C.DEFAULT_ROLES_PATH,
2014-03-17 19:52:44 +01:00
help='The path to the directory containing your roles. '
2013-12-19 02:15:37 +01:00
'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(
2014-01-29 02:29:24 +01:00
'-s', '--server', dest='api_server', default="galaxy.ansible.com",
2013-12-19 02:15:37 +01:00
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
2013-12-20 17:43:38 +01:00
if k == "roles_path":
2013-12-20 18:13:22 +01:00
if os.pathsep in data:
data = data.split(os.pathsep)[0]
2013-12-19 02:15:37 +01:00
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):
2014-08-22 00:48:02 +02:00
print '- you can use --ignore-errors to skip failed roles.'
2013-12-19 02:15:37 +01:00
sys.exit(rc)
2014-11-12 22:23:49 +01:00
2013-12-19 02:15:37 +01:00
#-------------------------------------------------------------------------------------
# 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
2015-09-17 06:14:32 +02:00
data = json.load(open_url(url))
2013-12-19 02:15:37 +01:00
if not data.get("current_version",None):
return None
else:
return data
except:
return None
2015-09-17 06:14:32 +02:00
def api_lookup_role_by_name(api_server, role_name, parser, notify=True):
2013-12-19 02:15:37 +01:00
"""
Uses the Galaxy API to do a lookup on the role owner/name.
"""
2013-12-20 15:55:53 +01:00
role_name = urllib.quote(role_name)
2013-12-19 02:15:37 +01:00
try:
2014-01-06 16:40:47 +01:00
parts = role_name.split(".")
user_name = ".".join(parts[0:-1])
role_name = parts[-1]
2014-11-12 22:23:49 +01:00
if notify:
print "- downloading role '%s', owned by %s" % (role_name, user_name)
2013-12-19 02:15:37 +01:00
except:
2013-12-20 21:27:24 +01:00
parser.print_help()
2014-08-22 00:48:02 +02:00
print "- invalid role name (%s). Specify role as format: username.rolename" % role_name
2013-12-19 02:15:37 +01:00
sys.exit(1)
url = 'https://%s/api/v1/roles/?owner__username=%s&name=%s' % (api_server,user_name,role_name)
try:
2015-09-17 06:14:32 +02:00
data = json.load(open_url(url))
2013-12-19 02:15:37 +01:00
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)
2015-09-17 06:14:32 +02:00
data = json.load(open_url(url))
2013-12-19 02:15:37 +01:00
results = data['results']
done = (data.get('next', None) == None)
while not done:
url = 'https://%s%s' % (api_server, data['next'])
print url
2015-09-17 06:14:32 +02:00
data = json.load(open_url(url))
2013-12-19 02:15:37 +01:00
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)
2015-09-17 06:14:32 +02:00
data = json.load(open_url(url))
2013-12-19 02:15:37 +01:00
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
2015-09-17 06:14:32 +02:00
data = json.load(open_url(url))
2013-12-19 02:15:37 +01:00
results += data['results']
done = (data.get('next', None) == None)
return results
except:
2014-08-22 00:48:02 +02:00
print "- failed to download the %s list" % what
2013-12-19 02:15:37 +01:00
return None
2014-08-13 13:32:36 +02:00
#-------------------------------------------------------------------------------------
# scm repo utility functions
#-------------------------------------------------------------------------------------
2014-08-15 13:19:40 +02:00
def scm_archive_role(scm, role_url, role_version, role_name):
2014-08-14 03:26:33 +02:00
if scm not in ['hg', 'git']:
2014-08-22 00:48:02 +02:00
print "- scm %s is not currently supported" % scm
2014-08-14 03:26:33 +02:00
return False
2014-08-13 13:32:36 +02:00
tempdir = tempfile.mkdtemp()
2014-08-15 13:19:40 +02:00
clone_cmd = [scm, 'clone', role_url, role_name]
2014-08-13 13:32:36 +02:00
with open('/dev/null', 'w') as devnull:
2014-08-21 22:56:58 +02:00
try:
2014-08-22 00:24:40 +02:00
print "- executing: %s" % " ".join(clone_cmd)
2014-08-21 22:56:58 +02:00
popen = subprocess.Popen(clone_cmd, cwd=tempdir, stdout=devnull, stderr=devnull)
except:
raise AnsibleError("error executing: %s" % " ".join(clone_cmd))
2014-08-13 13:32:36 +02:00
rc = popen.wait()
if rc != 0:
2014-08-22 00:24:40 +02:00
print "- command %s failed" % ' '.join(clone_cmd)
print " in directory %s" % tempdir
2014-08-14 03:26:33 +02:00
return False
2014-08-13 13:32:36 +02:00
2014-08-19 08:06:46 +02:00
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.tar')
2014-08-13 13:32:36 +02:00
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:
2014-08-22 00:24:40 +02:00
print "- executing: %s" % " ".join(archive_cmd)
2014-08-13 13:32:36 +02:00
popen = subprocess.Popen(archive_cmd, cwd=os.path.join(tempdir, role_name),
stderr=devnull, stdout=devnull)
rc = popen.wait()
if rc != 0:
2014-08-22 00:24:40 +02:00
print "- command %s failed" % ' '.join(archive_cmd)
print " in directory %s" % tempdir
2014-08-14 03:26:33 +02:00
return False
2014-08-13 13:32:36 +02:00
2015-02-09 14:33:48 +01:00
shutil.rmtree(tempdir, ignore_errors=True)
2014-08-13 13:32:36 +02:00
return temp_file.name
2013-12-19 02:15:37 +01:00
#-------------------------------------------------------------------------------------
# Role utility functions
#-------------------------------------------------------------------------------------
def get_role_path(role_name, options):
"""
Returns the role path based on the roles_path option
and the role name.
"""
2013-12-19 04:40:58 +01:00
roles_path = get_opt(options,'roles_path')
2013-12-19 05:22:44 +01:00
roles_path = os.path.join(roles_path, role_name)
roles_path = os.path.expanduser(roles_path)
return roles_path
2013-12-19 02:15:37 +01:00
def get_role_metadata(role_name, options):
"""
Returns the metadata as YAML, if the file 'meta/main.yml'
exists in the specified role_path
"""
2013-12-19 04:40:58 +01:00
role_path = os.path.join(get_role_path(role_name, options), 'meta/main.yml')
2013-12-19 02:15:37 +01:00
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.
"""
2015-11-22 17:39:02 +01:00
info_data = None
2013-12-19 02:15:37 +01:00
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()
except:
2015-11-22 17:39:02 +01:00
pass
return info_data
2013-12-19 02:15:37 +01:00
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)
2015-11-22 17:39:02 +01:00
f.write(info_data)
2013-12-19 02:15:37 +01:00
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):
2013-12-19 05:22:44 +01:00
role_path = get_role_path(role_name, options)
shutil.rmtree(role_path)
2013-12-19 02:15:37 +01:00
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
2014-08-15 13:19:40 +02:00
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)
2014-08-22 00:48:02 +02:00
print "- downloading role from %s" % archive_url
2013-12-19 02:15:37 +01:00
try:
2015-09-17 06:14:32 +02:00
url_file = open_url(archive_url)
2013-12-19 02:15:37 +01:00
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
2015-11-22 17:39:02 +01:00
print "- error: failed to download the file: %s" % str(e)
2013-12-19 02:15:37 +01:00
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
2014-08-21 22:56:58 +02:00
2013-12-19 02:15:37 +01:00
if not tarfile.is_tarfile(role_filename):
2014-08-22 00:48:02 +02:00
print "- error: the file downloaded was not a tar.gz"
2013-12-19 02:15:37 +01:00
return False
else:
2014-08-19 08:06:46 +02:00
if role_filename.endswith('.gz'):
role_tar_file = tarfile.open(role_filename, "r:gz")
else:
role_tar_file = tarfile.open(role_filename, "r")
2013-12-19 02:15:37 +01:00
# verify the role's meta file
meta_file = None
members = role_tar_file.getmembers()
2014-08-05 20:29:43 +02:00
# next find the metadata file
2013-12-19 02:15:37 +01:00
for member in members:
if "/meta/main.yml" in member.name:
meta_file = member
break
if not meta_file:
2014-08-22 00:48:02 +02:00
print "- error: this role does not appear to have a meta/main.yml file."
2013-12-19 02:15:37 +01:00
return False
else:
try:
meta_file_data = yaml.safe_load(role_tar_file.extractfile(meta_file))
except:
2014-08-22 00:48:02 +02:00
print "- error: this role does not appear to have a valid meta/main.yml file."
2013-12-19 02:15:37 +01:00
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
2014-08-28 01:24:26 +02:00
role_path = os.path.join(get_opt(options, 'roles_path'), role_name)
2013-12-19 05:22:44 +01:00
role_path = os.path.expanduser(role_path)
2014-08-22 00:48:02 +02:00
print "- extracting %s to %s" % (role_name, role_path)
2013-12-19 02:15:37 +01:00
try:
if os.path.exists(role_path):
if not os.path.isdir(role_path):
2014-08-22 00:48:02 +02:00
print "- error: the specified roles path exists and is not a directory."
2013-12-19 02:15:37 +01:00
return False
elif not get_opt(options, "force", False):
2014-08-22 00:48:02 +02:00
print "- error: the specified role %s appears to already exist. Use --force to replace it." % role_name
2013-12-19 02:15:37 +01:00
return False
else:
# using --force, remove the old path
if not remove_role(role_name, options):
2014-08-22 00:48:02 +02:00
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."
2013-12-19 02:15:37 +01:00
return False
else:
os.makedirs(role_path)
# now we do the actual extraction to the role_path
for member in members:
2014-08-05 20:29:43 +02:00
# 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
2013-12-19 02:15:37 +01:00
if member.isreg():
2014-08-05 20:29:43 +02:00
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)
2013-12-19 02:15:37 +01:00
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:
2015-11-22 17:39:02 +01:00
print "- error: you do not have permission to modify files in %s: %s" % (role_path, str(e))
2013-12-19 02:15:37 +01:00
return False
# return the parsed yaml metadata
2014-08-22 00:48:02 +02:00
print "- %s was installed successfully" % role_name
2013-12-19 02:15:37 +01:00
return meta_file_data
#-------------------------------------------------------------------------------------
# Action functions
#-------------------------------------------------------------------------------------
2013-12-20 21:27:24 +01:00
def execute_init(args, options, parser):
2013-12-19 02:15:37 +01:00
"""
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', './')
2014-01-29 02:29:24 +01:00
api_server = get_opt(options, "api_server", "galaxy.ansible.com")
2013-12-19 02:15:37 +01:00
force = get_opt(options, 'force', False)
2014-08-19 11:54:48 +02:00
offline = get_opt(options, 'offline', False)
2013-12-19 02:15:37 +01:00
2014-08-19 11:54:48 +02:00
if not offline:
api_config = api_get_config(api_server)
if not api_config:
2014-08-22 00:48:02 +02:00
print "- the API server (%s) is not responding, please try again later." % api_server
2014-08-19 11:54:48 +02:00
sys.exit(1)
2013-12-19 02:15:37 +01:00
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):
2014-08-22 00:48:02 +02:00
print "- the path %s already exists, but is a file - aborting" % role_path
2013-12-19 02:15:37 +01:00
sys.exit(1)
elif not force:
2014-08-22 00:48:02 +02:00
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."
2013-12-19 02:15:37 +01:00
sys.exit(1)
except Exception, e:
2013-12-20 21:27:24 +01:00
parser.print_help()
2015-11-22 17:39:02 +01:00
print "- could not init specified role name: %s" % str(e)
2013-12-19 02:15:37 +01:00
sys.exit(1)
ROLE_DIRS = ('defaults','files','handlers','meta','tasks','templates','vars')
2013-12-20 17:29:08 +01:00
# 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
2013-12-19 02:15:37 +01:00
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)
2013-12-20 17:29:08 +01:00
2013-12-19 02:15:37 +01:00
# 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
2014-08-19 11:54:48 +02:00
platforms = []
if not offline:
platforms = api_get_list(api_server, "platforms") or []
categories = []
if not offline:
categories = api_get_list(api_server, "categories") or []
2014-11-12 22:23:49 +01:00
2013-12-19 02:15:37 +01:00
# 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)',
2015-02-10 14:44:45 +01:00
issue_tracker_url = 'http://example.com/issue/tracker',
2013-12-19 19:58:07 +01:00
min_ansible_version = '1.2',
2013-12-19 02:15:37 +01:00
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
2013-12-20 16:26:42 +01:00
elif dir not in ('files','templates'):
2013-12-19 02:15:37 +01:00
# 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()
2014-08-22 00:48:02 +02:00
print "- %s was created successfully" % role_name
2013-12-19 02:15:37 +01:00
2013-12-20 21:27:24 +01:00
def execute_info(args, options, parser):
2013-12-19 02:15:37 +01:00
"""
Executes the info action. This action prints out detailed
information about an installed role as well as info available
from the galaxy API.
"""
2014-11-12 22:23:49 +01:00
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']
2014-11-12 23:29:11 +01:00
del install_info['version']
2014-11-12 22:23:49 +01:00
role_info.update(install_info)
2015-09-17 06:14:32 +02:00
remote_data = api_lookup_role_by_name(api_server, role, parser, False)
2014-11-12 22:23:49 +01:00
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
2013-12-19 02:15:37 +01:00
2013-12-20 21:27:24 +01:00
def execute_install(args, options, parser):
2013-12-19 02:15:37 +01:00
"""
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)
2014-11-12 22:23:49 +01:00
if len(args) == 0 and role_file is None:
2013-12-19 02:15:37 +01:00
# the user needs to specify one of either --role-file
# or specify a single user/role name
2013-12-20 21:27:24 +01:00
parser.print_help()
2014-08-22 00:48:02 +02:00
print "- you must specify a user/role name or a roles file"
2013-12-19 02:15:37 +01:00
sys.exit()
2014-11-12 22:23:49 +01:00
elif len(args) == 1 and not role_file is None:
2013-12-19 02:15:37 +01:00
# using a role file is mutually exclusive of specifying
# the role name on the command line
2013-12-20 21:27:24 +01:00
parser.print_help()
2014-08-22 00:48:02 +02:00
print "- please specify a user/role name, or a roles file, but not both"
2013-12-19 02:15:37 +01:00
sys.exit(1)
2014-11-12 22:23:49 +01:00
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")
2013-12-19 02:15:37 +01:00
if role_file:
f = open(role_file, 'r')
2014-08-18 13:27:41 +02:00
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())
2013-12-19 02:15:37 +01:00
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).
2014-08-19 11:54:48 +02:00
roles_left = map(ansible.utils.role_spec_parse, args)
2013-12-19 02:15:37 +01:00
while len(roles_left) > 0:
# query the galaxy API for the role data
2014-08-15 13:19:40 +02:00
role_data = None
2014-08-18 13:27:41 +02:00
role = roles_left.pop(0)
role_src = role.get("src")
role_scm = role.get("scm")
2014-08-28 01:24:26 +02:00
role_path = role.get("path")
if role_path:
options.roles_path = role_path
else:
options.roles_path = roles_path
2013-12-19 02:15:37 +01:00
2014-08-15 13:19:40 +02:00
if os.path.isfile(role_src):
2013-12-19 02:15:37 +01:00
# installing a local tar.gz
2014-08-15 13:19:40 +02:00
tmp_file = role_src
2013-12-19 02:15:37 +01:00
else:
2014-08-18 13:27:41 +02:00
if role_scm:
2014-08-14 03:26:33 +02:00
# create tar file from scm url
2014-08-18 13:27:41 +02:00
tmp_file = scm_archive_role(role_scm, role_src, role.get("version"), role.get("name"))
2014-08-15 13:19:40 +02:00
elif '://' in role_src:
# just download a URL - version will probably be in the URL
tmp_file = fetch_role(role_src, None, None, options)
2013-12-19 02:15:37 +01:00
else:
2014-08-15 13:19:40 +02:00
# installing from galaxy
2014-08-19 11:54:48 +02:00
api_config = api_get_config(api_server)
if not api_config:
2014-08-22 00:48:02 +02:00
print "- the API server (%s) is not responding, please try again later." % api_server
2014-08-19 11:54:48 +02:00
sys.exit(1)
2015-09-17 06:14:32 +02:00
role_data = api_lookup_role_by_name(api_server, role_src, parser)
2014-08-13 13:32:36 +02:00
if not role_data:
2014-08-22 00:48:02 +02:00
print "- sorry, %s was not found on %s." % (role_src, api_server)
2014-09-09 21:57:23 +02:00
exit_without_ignore(options)
2013-12-19 02:15:37 +01:00
continue
2014-08-13 13:32:36 +02:00
role_versions = api_fetch_role_related(api_server, 'versions', role_data['id'])
2014-10-09 09:11:46 +02:00
if "version" not in role or role['version'] == '':
2014-08-13 13:32:36 +02:00
# 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()
2014-08-18 13:27:41 +02:00
role["version"] = str(loose_versions[-1])
2014-08-13 13:32:36 +02:00
else:
2014-08-18 13:27:41 +02:00
role["version"] = 'master'
2014-09-08 20:20:29 +02:00
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)
2014-08-13 13:32:36 +02:00
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.
2014-08-18 13:27:41 +02:00
tmp_file = fetch_role(role_src, role["version"], role_data, options)
2014-08-22 00:48:02 +02:00
installed = False
if tmp_file:
2014-08-22 15:57:10 +02:00
installed = install_role(role.get("name"), role.get("version"), tmp_file, options)
2014-08-15 13:19:40 +02:00
# we're done with the temp file, clean it up
2014-09-13 12:27:47 +02:00
if tmp_file != role_src:
os.unlink(tmp_file)
2014-08-15 13:19:40 +02:00
# install dependencies, if we want them
2014-09-05 19:46:33 +02:00
if not no_deps and installed:
2014-08-15 13:19:40 +02:00
if not role_data:
2014-08-18 13:27:41 +02:00
role_data = get_role_metadata(role.get("name"), options)
2015-11-22 17:39:02 +01:00
role_dependencies = role_data.get('dependencies',[])
2014-08-15 13:19:40 +02:00
else:
2015-11-22 17:39:02 +01:00
role_dependencies = role_data['summary_fields'].get('dependencies',[]) # api_fetch_role_related(api_server, 'dependencies', role_data['id'])
2015-06-03 04:31:53 +02:00
if not role_dependencies:
role_dependencies = []
2014-08-18 13:27:41 +02:00
for dep in role_dependencies:
2014-09-04 16:34:32 +02:00
if isinstance(dep, basestring):
2014-08-18 13:27:41 +02:00
dep = ansible.utils.role_spec_parse(dep)
else:
dep = ansible.utils.role_yaml_parse(dep)
if not get_role_metadata(dep["name"], options):
2014-09-05 19:46:33 +02:00
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"]
2014-08-13 13:32:36 +02:00
else:
2014-08-22 00:48:02 +02:00
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")
2014-08-15 13:19:40 +02:00
exit_without_ignore(options)
2013-12-19 02:15:37 +01:00
sys.exit(0)
2013-12-20 21:27:24 +01:00
def execute_remove(args, options, parser):
2013-12-19 02:15:37 +01:00
"""
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:
2013-12-20 21:27:24 +01:00
parser.print_help()
2014-08-22 00:48:02 +02:00
print '- you must specify at least one role to remove.'
2013-12-19 02:15:37 +01:00
sys.exit()
for role in args:
if get_role_metadata(role, options):
if remove_role(role, options):
2014-08-22 00:48:02 +02:00
print '- successfully removed %s' % role
2013-12-19 02:15:37 +01:00
else:
2014-08-22 00:48:02 +02:00
print "- failed to remove role: %s" % role
2013-12-19 02:15:37 +01:00
else:
2014-08-22 00:48:02 +02:00
print '- %s is not installed, skipping.' % role
2013-12-19 02:15:37 +01:00
sys.exit(0)
2013-12-20 21:27:24 +01:00
def execute_list(args, options, parser):
2013-12-19 02:15:37 +01:00
"""
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:
2014-08-22 00:48:02 +02:00
print "- please specify only one role to list, or specify no roles to see a full list"
2013-12-19 02:15:37 +01:00
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
2014-08-22 00:48:02 +02:00
print "- %s, %s" % (role_name, version)
2013-12-19 02:15:37 +01:00
else:
2014-08-22 00:48:02 +02:00
print "- the role %s was not found" % role_name
2013-12-19 02:15:37 +01:00
else:
# show all valid roles in the roles_path directory
2013-12-19 04:40:58 +01:00
roles_path = get_opt(options, 'roles_path')
2013-12-19 05:22:44 +01:00
roles_path = os.path.expanduser(roles_path)
2013-12-19 02:15:37 +01:00
if not os.path.exists(roles_path):
2013-12-20 21:27:24 +01:00
parser.print_help()
2014-08-22 00:48:02 +02:00
print "- the path %s does not exist. Please specify a valid path with --roles-path" % roles_path
2013-12-19 02:15:37 +01:00
sys.exit(1)
elif not os.path.isdir(roles_path):
2014-08-22 00:48:02 +02:00
print "- %s exists, but it is not a directory. Please specify a valid path with --roles-path" % roles_path
2013-12-20 21:27:24 +01:00
parser.print_help()
2013-12-19 02:15:37 +01:00
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)"
2014-08-22 00:48:02 +02:00
print "- %s, %s" % (path_file, version)
2013-12-19 02:15:37 +01:00
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]
2013-12-20 21:27:24 +01:00
fn(args, options, parser)
2013-12-19 02:15:37 +01:00
#except KeyError, e:
2014-08-22 00:48:02 +02:00
# print "- error: %s is not a valid action. Valid actions are: %s" % (action, ", ".join(VALID_ACTIONS))
2013-12-19 02:15:37 +01:00
# sys.exit(1)
if __name__ == "__main__":
main()