mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
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
|
||||||
import os.path
|
import os.path
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import tarfile
|
|
||||||
import tempfile
|
|
||||||
import urllib
|
|
||||||
import urllib2
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
from jinja2 import Environment
|
from jinja2 import Environment
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import ansible.constants as C
|
import ansible.constants as C
|
||||||
import ansible.utils
|
import ansible.utils
|
||||||
|
@ -46,12 +37,12 @@ from ansible.galaxy import Galaxy
|
||||||
from ansible.galaxy.api import GalaxyAPI
|
from ansible.galaxy.api import GalaxyAPI
|
||||||
from ansible.galaxy.role import GalaxyRole
|
from ansible.galaxy.role import GalaxyRole
|
||||||
from ansible.playbook.role.requirement import RoleRequirement
|
from ansible.playbook.role.requirement import RoleRequirement
|
||||||
from ansible.utils.display import Display
|
|
||||||
|
|
||||||
class GalaxyCLI(CLI):
|
class GalaxyCLI(CLI):
|
||||||
|
|
||||||
VALID_ACTIONS = ("init", "info", "install", "list", "remove")
|
VALID_ACTIONS = ("init", "info", "install", "list", "remove", "search")
|
||||||
SKIP_INFO_KEYS = ("platforms","readme_html", "related", "summary_fields", "average_aw_composite", "average_aw_score", "url" )
|
SKIP_INFO_KEYS = ("name", "description", "readme_html", "related", "summary_fields", "average_aw_composite", "average_aw_score", "url" )
|
||||||
|
|
||||||
def __init__(self, args, display=None):
|
def __init__(self, args, display=None):
|
||||||
|
|
||||||
|
@ -75,44 +66,43 @@ class GalaxyCLI(CLI):
|
||||||
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":
|
elif self.action == "init":
|
||||||
self.parser.set_usage("usage: %prog init [options] role_name")
|
self.parser.set_usage("usage: %prog init [options] role_name")
|
||||||
self.parser.add_option(
|
self.parser.add_option('-p', '--init-path', dest='init_path', default="./",
|
||||||
'-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.')
|
||||||
help='The path in which the skeleton role will be created. '
|
|
||||||
'The default is the current working directory.')
|
|
||||||
self.parser.add_option(
|
self.parser.add_option(
|
||||||
'--offline', dest='offline', default=False, action='store_true',
|
'--offline', dest='offline', default=False, action='store_true',
|
||||||
help="Don't query the galaxy API when creating roles")
|
help="Don't query the galaxy API when creating roles")
|
||||||
elif self.action == "install":
|
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.set_usage("usage: %prog install [options] [-r FILE | role_name(s)[,version] | scm+role_repo_url[,version] | tar_file(s)]")
|
||||||
self.parser.add_option(
|
self.parser.add_option('-i', '--ignore-errors', dest='ignore_errors', action='store_true', default=False,
|
||||||
'-i', '--ignore-errors', dest='ignore_errors', action='store_true', default=False,
|
|
||||||
help='Ignore errors and continue with the next specified role.')
|
help='Ignore errors and continue with the next specified role.')
|
||||||
self.parser.add_option(
|
self.parser.add_option('-n', '--no-deps', dest='no_deps', action='store_true', default=False,
|
||||||
'-n', '--no-deps', dest='no_deps', action='store_true', default=False,
|
|
||||||
help='Don\'t download roles listed as dependencies')
|
help='Don\'t download roles listed as dependencies')
|
||||||
self.parser.add_option(
|
self.parser.add_option('-r', '--role-file', dest='role_file',
|
||||||
'-r', '--role-file', dest='role_file',
|
|
||||||
help='A file containing a list of roles to be imported')
|
help='A file containing a list of roles to be imported')
|
||||||
elif self.action == "remove":
|
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":
|
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
|
# options that apply to more than one action
|
||||||
if self.action != "init":
|
if self.action != "init":
|
||||||
self.parser.add_option(
|
self.parser.add_option('-p', '--roles-path', dest='roles_path', default=C.DEFAULT_ROLES_PATH,
|
||||||
'-p', '--roles-path', dest='roles_path', default=C.DEFAULT_ROLES_PATH,
|
|
||||||
help='The path to the directory containing your roles. '
|
help='The path to the directory containing your roles. '
|
||||||
'The default is the roles_path configured in your '
|
'The default is the roles_path configured in your '
|
||||||
'ansible.cfg file (/etc/ansible/roles if not configured)')
|
'ansible.cfg file (/etc/ansible/roles if not configured)')
|
||||||
|
|
||||||
if self.action in ("info","init","install"):
|
if self.action in ("info","init","install","search"):
|
||||||
self.parser.add_option('-s', '--server', dest='api_server', default="https://galaxy.ansible.com",
|
self.parser.add_option('-s', '--server', dest='api_server', default="https://galaxy.ansible.com",
|
||||||
help='The API server destination')
|
help='The API server destination')
|
||||||
|
|
||||||
if self.action in ("init","install"):
|
if self.action in ("init","install"):
|
||||||
self.parser.add_option(
|
self.parser.add_option('-f', '--force', dest='force', action='store_true', default=False,
|
||||||
'-f', '--force', dest='force', action='store_true', default=False,
|
|
||||||
help='Force overwriting an existing role')
|
help='Force overwriting an existing role')
|
||||||
|
|
||||||
# get options, args and galaxy object
|
# get options, args and galaxy object
|
||||||
|
@ -127,7 +117,7 @@ class GalaxyCLI(CLI):
|
||||||
super(GalaxyCLI, self).run()
|
super(GalaxyCLI, self).run()
|
||||||
|
|
||||||
# if not offline, get connect to galaxy api
|
# 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
|
api_server = self.options.api_server
|
||||||
self.api = GalaxyAPI(self.galaxy, api_server)
|
self.api = GalaxyAPI(self.galaxy, api_server)
|
||||||
if not self.api:
|
if not self.api:
|
||||||
|
@ -156,6 +146,65 @@ class GalaxyCLI(CLI):
|
||||||
if not self.get_opt("ignore_errors", False):
|
if not self.get_opt("ignore_errors", False):
|
||||||
raise AnsibleError('- you can use --ignore-errors to skip failed roles and finish processing the list.')
|
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):
|
def execute_init(self):
|
||||||
"""
|
"""
|
||||||
Executes the init action, which creates the skeleton framework
|
Executes the init action, which creates the skeleton framework
|
||||||
|
@ -249,6 +298,7 @@ class GalaxyCLI(CLI):
|
||||||
|
|
||||||
roles_path = self.get_opt("roles_path")
|
roles_path = self.get_opt("roles_path")
|
||||||
|
|
||||||
|
data = ''
|
||||||
for role in self.args:
|
for role in self.args:
|
||||||
|
|
||||||
role_info = {}
|
role_info = {}
|
||||||
|
@ -277,23 +327,12 @@ class GalaxyCLI(CLI):
|
||||||
if role_spec:
|
if role_spec:
|
||||||
role_info.update(role_spec)
|
role_info.update(role_spec)
|
||||||
|
|
||||||
if role_info:
|
data += self._display_role_info(role_info)
|
||||||
self.display.display("- %s:" % (role))
|
if data:
|
||||||
for k in sorted(role_info.keys()):
|
data += "\n- %s:" % (role)
|
||||||
|
|
||||||
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:
|
else:
|
||||||
self.display.display("\t%s: %s" % (k, role_info[k]))
|
data += "\n- the role %s was not found" % role
|
||||||
else:
|
self.pager(data)
|
||||||
self.display.display("- the role %s was not found" % role)
|
|
||||||
|
|
||||||
def execute_install(self):
|
def execute_install(self):
|
||||||
"""
|
"""
|
||||||
|
@ -497,35 +536,22 @@ class GalaxyCLI(CLI):
|
||||||
self.display.display("- %s, %s" % (path_file, version))
|
self.display.display("- %s, %s" % (path_file, version))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def parse_requirements_files(self, role):
|
def execute_search(self):
|
||||||
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"]:
|
search = None
|
||||||
(scm, src) = role["src"].split('+')
|
if len(self.args) > 1:
|
||||||
role["scm"] = scm
|
raise AnsibleOptionsError("At most a single search term is allowed.")
|
||||||
role["src"] = src
|
elif len(self.args) == 1:
|
||||||
|
search = self.args.pop()
|
||||||
|
|
||||||
if 'name' not in role:
|
response = self.api.search_roles(search, self.options.platforms, self.options.categories)
|
||||||
role["name"] = GalaxyRole.url_to_spec(role["src"])
|
|
||||||
|
|
||||||
if 'version' not in role:
|
if 'count' in response:
|
||||||
role['version'] = ''
|
self.galaxy.display.display("Found %d roles matching your search:\n" % response['count'])
|
||||||
|
|
||||||
if 'scm' not in role:
|
data = ''
|
||||||
role['scm'] = None
|
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
|
import json
|
||||||
from urllib2 import urlopen, quote as urlquote
|
from urllib2 import urlopen, quote as urlquote, HTTPError
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
|
||||||
from ansible.errors import AnsibleError
|
from ansible.errors import AnsibleError, AnsibleOptionsError
|
||||||
|
|
||||||
class GalaxyAPI(object):
|
class GalaxyAPI(object):
|
||||||
''' This class is meant to be used as a API client for an Ansible Galaxy server '''
|
''' 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
|
return results
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
raise AnsibleError("Failed to download the %s list: %s" % (what, str(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