draft galaxy cli search
TODO: paging results
This commit is contained in:
parent
e282309f6d
commit
6ffd9c3025
2 changed files with 137 additions and 80 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue