draft galaxy cli search

TODO: paging results
This commit is contained in:
Brian Coca 2015-08-22 02:28:03 -04:00
parent e282309f6d
commit 6ffd9c3025
2 changed files with 137 additions and 80 deletions

View file

@ -19,23 +19,14 @@
#
########################################################################
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
@ -46,12 +37,12 @@ from ansible.galaxy import Galaxy
from ansible.galaxy.api import GalaxyAPI
from ansible.galaxy.role import GalaxyRole
from ansible.playbook.role.requirement import RoleRequirement
from ansible.utils.display import Display
class GalaxyCLI(CLI):
VALID_ACTIONS = ("init", "info", "install", "list", "remove")
SKIP_INFO_KEYS = ("platforms","readme_html", "related", "summary_fields", "average_aw_composite", "average_aw_score", "url" )
VALID_ACTIONS = ("init", "info", "install", "list", "remove", "search")
SKIP_INFO_KEYS = ("name", "description", "readme_html", "related", "summary_fields", "average_aw_composite", "average_aw_score", "url" )
def __init__(self, args, display=None):
@ -72,47 +63,46 @@ class GalaxyCLI(CLI):
# options specific to actions
if self.action == "info":
self.parser.set_usage("usage: %prog info [options] role_name[,version]")
self.parser.set_usage("usage: %prog info [options] role_name[,version]")
elif self.action == "init":
self.parser.set_usage("usage: %prog init [options] role_name")
self.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.')
self.parser.add_option(
self.parser.set_usage("usage: %prog init [options] role_name")
self.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.')
self.parser.add_option(
'--offline', dest='offline', default=False, action='store_true',
help="Don't query the galaxy API when creating roles")
elif self.action == "install":
self.parser.set_usage("usage: %prog install [options] [-r FILE | role_name(s)[,version] | scm+role_repo_url[,version] | tar_file(s)]")
self.parser.add_option(
'-i', '--ignore-errors', dest='ignore_errors', action='store_true', default=False,
self.parser.set_usage("usage: %prog install [options] [-r FILE | role_name(s)[,version] | scm+role_repo_url[,version] | tar_file(s)]")
self.parser.add_option('-i', '--ignore-errors', dest='ignore_errors', action='store_true', default=False,
help='Ignore errors and continue with the next specified role.')
self.parser.add_option(
'-n', '--no-deps', dest='no_deps', action='store_true', default=False,
self.parser.add_option('-n', '--no-deps', dest='no_deps', action='store_true', default=False,
help='Don\'t download roles listed as dependencies')
self.parser.add_option(
'-r', '--role-file', dest='role_file',
self.parser.add_option('-r', '--role-file', dest='role_file',
help='A file containing a list of roles to be imported')
elif self.action == "remove":
self.parser.set_usage("usage: %prog remove role1 role2 ...")
self.parser.set_usage("usage: %prog remove role1 role2 ...")
elif self.action == "list":
self.parser.set_usage("usage: %prog list [role_name]")
self.parser.set_usage("usage: %prog list [role_name]")
elif self.action == "search":
self.parser.add_option('-P', '--platforms', dest='platforms',
help='list of OS platforms to filter by')
self.parser.add_option('-C', '--categories', dest='categories',
help='list of categories to filter by')
self.parser.set_usage("usage: %prog search [<search_term>] [-C <category1,category2>] [-P platform]")
# options that apply to more than one action
if self.action != "init":
self.parser.add_option(
'-p', '--roles-path', dest='roles_path', default=C.DEFAULT_ROLES_PATH,
self.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 self.action in ("info","init","install"):
self.parser.add_option( '-s', '--server', dest='api_server', default="https://galaxy.ansible.com",
if self.action in ("info","init","install","search"):
self.parser.add_option('-s', '--server', dest='api_server', default="https://galaxy.ansible.com",
help='The API server destination')
if self.action in ("init","install"):
self.parser.add_option(
'-f', '--force', dest='force', action='store_true', default=False,
self.parser.add_option('-f', '--force', dest='force', action='store_true', default=False,
help='Force overwriting an existing role')
# get options, args and galaxy object
@ -127,7 +117,7 @@ class GalaxyCLI(CLI):
super(GalaxyCLI, self).run()
# if not offline, get connect to galaxy api
if self.action in ("info","install") or (self.action == 'init' and not self.options.offline):
if self.action in ("info","install", "search") or (self.action == 'init' and not self.options.offline):
api_server = self.options.api_server
self.api = GalaxyAPI(self.galaxy, api_server)
if not self.api:
@ -156,6 +146,65 @@ class GalaxyCLI(CLI):
if not self.get_opt("ignore_errors", False):
raise AnsibleError('- you can use --ignore-errors to skip failed roles and finish processing the list.')
def parse_requirements_files(self, role):
if 'role' in role:
# Old style: {role: "galaxy.role,version,name", other_vars: "here" }
role_info = role_spec_parse(role['role'])
if isinstance(role_info, dict):
# Warning: Slight change in behaviour here. name may be being
# overloaded. Previously, name was only a parameter to the role.
# Now it is both a parameter to the role and the name that
# ansible-galaxy will install under on the local system.
if 'name' in role and 'name' in role_info:
del role_info['name']
role.update(role_info)
else:
# New style: { src: 'galaxy.role,version,name', other_vars: "here" }
if 'github.com' in role["src"] and 'http' in role["src"] and '+' not in role["src"] and not role["src"].endswith('.tar.gz'):
role["src"] = "git+" + role["src"]
if '+' in role["src"]:
(scm, src) = role["src"].split('+')
role["scm"] = scm
role["src"] = src
if 'name' not in role:
role["name"] = GalaxyRole.url_to_spec(role["src"])
if 'version' not in role:
role['version'] = ''
if 'scm' not in role:
role['scm'] = None
return role
def _display_role_info(self, role_info):
text = "\nRole: %s \n" % role_info['name']
text += "\tdescription: %s \n" % role_info['description']
for k in sorted(role_info.keys()):
if k in self.SKIP_INFO_KEYS:
continue
if isinstance(role_info[k], dict):
text += "\t%s: \n" % (k)
for key in sorted(role_info[k].keys()):
if key in self.SKIP_INFO_KEYS:
continue
text += "\t\t%s: %s\n" % (key, role_info[k][key])
else:
text += "\t%s: %s\n" % (k, role_info[k])
return text
############################
# execute actions
############################
def execute_init(self):
"""
Executes the init action, which creates the skeleton framework
@ -249,6 +298,7 @@ class GalaxyCLI(CLI):
roles_path = self.get_opt("roles_path")
data = ''
for role in self.args:
role_info = {}
@ -277,23 +327,12 @@ class GalaxyCLI(CLI):
if role_spec:
role_info.update(role_spec)
if role_info:
self.display.display("- %s:" % (role))
for k in sorted(role_info.keys()):
if k in self.SKIP_INFO_KEYS:
continue
if isinstance(role_info[k], dict):
self.display.display("\t%s: " % (k))
for key in sorted(role_info[k].keys()):
if key in self.SKIP_INFO_KEYS:
continue
self.display.display("\t\t%s: %s" % (key, role_info[k][key]))
else:
self.display.display("\t%s: %s" % (k, role_info[k]))
data += self._display_role_info(role_info)
if data:
data += "\n- %s:" % (role)
else:
self.display.display("- the role %s was not found" % role)
data += "\n- the role %s was not found" % role
self.pager(data)
def execute_install(self):
"""
@ -497,35 +536,22 @@ class GalaxyCLI(CLI):
self.display.display("- %s, %s" % (path_file, version))
return 0
def parse_requirements_files(self, role):
if 'role' in role:
# Old style: {role: "galaxy.role,version,name", other_vars: "here" }
role_info = role_spec_parse(role['role'])
if isinstance(role_info, dict):
# Warning: Slight change in behaviour here. name may be being
# overloaded. Previously, name was only a parameter to the role.
# Now it is both a parameter to the role and the name that
# ansible-galaxy will install under on the local system.
if 'name' in role and 'name' in role_info:
del role_info['name']
role.update(role_info)
else:
# New style: { src: 'galaxy.role,version,name', other_vars: "here" }
if 'github.com' in role["src"] and 'http' in role["src"] and '+' not in role["src"] and not role["src"].endswith('.tar.gz'):
role["src"] = "git+" + role["src"]
def execute_search(self):
if '+' in role["src"]:
(scm, src) = role["src"].split('+')
role["scm"] = scm
role["src"] = src
search = None
if len(self.args) > 1:
raise AnsibleOptionsError("At most a single search term is allowed.")
elif len(self.args) == 1:
search = self.args.pop()
if 'name' not in role:
role["name"] = GalaxyRole.url_to_spec(role["src"])
response = self.api.search_roles(search, self.options.platforms, self.options.categories)
if 'version' not in role:
role['version'] = ''
if 'count' in response:
self.galaxy.display.display("Found %d roles matching your search:\n" % response['count'])
if 'scm' not in role:
role['scm'] = None
data = ''
if 'results' in response:
for role in response['results']:
data += self._display_role_info(role)
return role
self.pager(data)

View file

@ -21,10 +21,10 @@
#
########################################################################
import json
from urllib2 import urlopen, quote as urlquote
from urllib2 import urlopen, quote as urlquote, HTTPError
from urlparse import urlparse
from ansible.errors import AnsibleError
from ansible.errors import AnsibleError, AnsibleOptionsError
class GalaxyAPI(object):
''' This class is meant to be used as a API client for an Ansible Galaxy server '''
@ -139,3 +139,34 @@ class GalaxyAPI(object):
return results
except Exception as error:
raise AnsibleError("Failed to download the %s list: %s" % (what, str(error)))
def search_roles(self, search, platforms=None, categories=None):
search_url = self.baseurl + '/roles/?page=1'
if search:
search_url += '&search=' + urlquote(search)
if categories is None:
categories = []
elif isinstance(categories, basestring):
categories = categories.split(',')
for cat in categories:
search_url += '&chain__categories__name=' + urlquote(cat)
if platforms is None:
platforms = []
elif isinstance(platforms, basestring):
platforms = platforms.split(',')
for plat in platforms:
search_url += '&chain__platforms__name=' + urlquote(plat)
self.galaxy.display.debug("Executing query: %s" % search_url)
try:
data = json.load(urlopen(search_url))
except HTTPError as e:
raise AnsibleError("Unsuccessful request to server: %s" % str(e))
return data