├── requirements.txt ├── .gitignore ├── setup.cfg ├── tests └── fixtures │ ├── extraroles.yml │ └── roles.yml ├── .travis.yml ├── setup.py ├── ansible-library.py ├── README.md └── ansible_library ├── __init__.py ├── application.py └── tests.py /requirements.txt: -------------------------------------------------------------------------------- 1 | Werkzeug>=0.11 2 | flask>=0.10 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | dist 3 | build 4 | 5 | *.pyc 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /tests/fixtures/extraroles.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: dnsmasq 4 | galaxy_info: 5 | version: v1.0 6 | description: Configure dnsmasq as local dns server 7 | company: Fever 8 | min_ansible_version: 1.9 9 | dependencies: [] 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | env: 4 | - ANSIBLE_VERSION=2.5.15 5 | - ANSIBLE_VERSION=2.7.7 6 | install: 7 | - pip install flask==0.10 8 | - pip install ansible==${ANSIBLE_VERSION} 9 | script: 10 | - python -m unittest discover 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from distutils.core import setup 4 | 5 | setup( 6 | name = 'ansible-library', 7 | version = '1.2.3', 8 | description = 'Minimal galaxy server to host private roles', 9 | author = 'Javier Palacios', 10 | author_email = 'javiplx@gmail.com', 11 | license = 'GPLv2', 12 | url = 'https://github.com/javiplx/ansible-library', 13 | scripts = [ 'ansible-library.py' ], 14 | packages = [ 'ansible_library' ], 15 | install_requires=[ 'Werkzeug>=0.9' , 'flask>=0.10' , 'ansible>=1.9.2' , 'pyyaml' ] 16 | ) 17 | 18 | -------------------------------------------------------------------------------- /ansible-library.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2016 Javier Palacios 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # Version 2 as published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, but 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See full 12 | # GPLv2 for more details (http://www.gnu.org/licenses/gpl-2.0.html). 13 | 14 | import ansible_library 15 | 16 | if __name__ == "__main__": 17 | ansible_library.app.run() 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/fixtures/roles.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | - name: djangoserver 5 | galaxy_info: 6 | version: 1.0 7 | description: Setup stack for running a django app 8 | company: Fever 9 | min_ansible_version: 1.9 10 | dependencies: 11 | - gitclone 12 | 13 | - name: djangoserver 14 | galaxy_info: 15 | version: 1.1 16 | description: Setup stack for running a django app 17 | company: Fever 18 | min_ansible_version: 1.9 19 | dependencies: 20 | - gitclone 21 | 22 | - name: fever 23 | galaxy_info: 24 | version: v1.0 25 | description: Setup the user to run fever applications 26 | company: Fever 27 | min_ansible_version: 1.9 28 | dependencies: [] 29 | 30 | - name: fever 31 | galaxy_info: 32 | version: v1.1 33 | description: Setup the user to run fever applications 34 | company: Fever 35 | author: Javier Palacios 36 | license: private 37 | min_ansible_version: 1.9 38 | dependencies: [] 39 | 40 | - name: gitclone 41 | galaxy_info: 42 | version: v1.0 43 | description: Pseudo-abstract role to clone a git@github.com:Feverup repository 44 | company: Fever 45 | min_ansible_version: 1.9 46 | dependencies: 47 | - fever 48 | 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Build Status](https://travis-ci.org/javiplx/ansible-library.svg?branch=master)](https://travis-ci.org/javiplx/ansible-library) 3 | 4 | # Ansible Library 5 | 6 | Ansible Library is a minimal galaxy implementation, intended to allow serve 7 | private roles from local storage and behave as proxy towards ansible galaxy. 8 | 9 | It is intended to allow role installs with standard tools, although it requires 10 | a minor patch to ansible-galaxy, that enables 11 | 12 | ansible-galaxy install --server=http://local-galaxy:3333 private-role,v1.4 13 | 14 | ## Usage 15 | 16 | Just download the server in your prefered location and run 17 | 18 | $ ansible-library.py 19 | 20 | which starts the server listening on port 3333. 21 | 22 | On startup, it searchs all files under a configurable directory, and uses the 23 | presence of `meta/main.yml` is used to decide whether it is a role or not. The 24 | name and version are assigned following the github download standards, that is 25 | 26 | /var/lib/galaxy//.tar.gz 27 | 28 | Default behaviour is to run as a foreground process, but it can fork itself and 29 | run as daemon process. 30 | 31 | ### Maintenance 32 | 33 | Besides the galaxy specific endpoints (`/api/v1/`), there is an additional 34 | operation for library maintenance. Making a PUT to `/api/reload`, the cache 35 | will be cleaned and the local roles reloaded from disk. This operation acts 36 | as a warm restart, and is mainly intended to make visible new role versions 37 | copied under the local roles directory. 38 | 39 | ### Role uploading 40 | 41 | Appart from copying roles into the library directory, it is possible to upload 42 | packed roles just by performing a PUT request with the role file to 43 | `//`, and the server will copy it to the right place and 44 | add to its list of available roles. 45 | 46 | ### Configuration 47 | 48 | Some internal parameters can be override by setting them on a configuration 49 | file, which is a yaml one named `/etc/ansible-library.yml`. The configurable 50 | parameters and their default values are 51 | 52 | roles_dir: /var/lib/galaxy 53 | logfile: None 54 | listen: 0.0.0.0 55 | port: 3333 56 | ttl: 3600 57 | daemonize: False 58 | piddir: None 59 | debug: False 60 | 61 | ## License 62 | 63 | Copyright (C) 2016 Javier Palacios 64 | 65 | This program is free software; you can redistribute it and/or 66 | modify it under the terms of the GNU General Public License 67 | Version 2 as published by the Free Software Foundation. 68 | 69 | This program is distributed in the hope that it will be useful, 70 | but WITHOUT ANY WARRANTY; without even the implied warranty of 71 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See full 72 | GPLv2 for more details (http://www.gnu.org/licenses/gpl-2.0.html). 73 | 74 | -------------------------------------------------------------------------------- /ansible_library/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (C) 2016 Javier Palacios 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # Version 2 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See full 11 | # GPLv2 for more details (http://www.gnu.org/licenses/gpl-2.0.html). 12 | 13 | import application 14 | import flask 15 | import os 16 | 17 | from ansible.module_utils.urls import open_url 18 | 19 | me = { "description": "ansible-library REST API", 20 | "current_version": "v1", 21 | "available_versions": {"v1": "/api/v1/"} 22 | } 23 | 24 | app = application.library() 25 | 26 | @app.route("/monitor/") 27 | def monitor(): 28 | return flask.jsonify({'msg': 'Running'}) , 200 29 | 30 | @app.route("/api/") 31 | def api(): 32 | return flask.jsonify(me) 33 | 34 | @app.route('/api/reload', methods=['PUT']) 35 | def reload(): 36 | flask.current_app.load_roles() 37 | return flask.jsonify({'msg': 'Done'}) 38 | 39 | @app.before_request 40 | def before_request(): 41 | for role in flask.current_app.roles : 42 | if role.expired() : 43 | flask.current_app.roles.remove( role ) 44 | 45 | @app.route("/api/v1/roles/") 46 | def get_roles(): 47 | user = flask.request.args.get('owner__username') 48 | name = flask.request.args.get('name') 49 | roles = flask.current_app.roles.by_name( name ) 50 | if not roles : 51 | galaxy_url = "https://galaxy.ansible.com%s" % flask.request.full_path 52 | response = flask.json.load(open_url(galaxy_url)) 53 | results = map( lambda x : application.proxied_role(x, flask.current_app.appconfig['ttl']) , response['results'] ) 54 | flask.current_app.roles.extend( results ) 55 | return flask.jsonify(response) 56 | resp = { "count": len(roles), 57 | "cur_page": len(roles), 58 | "num_pages": len(roles), 59 | "next": None, 60 | "previous": None, 61 | "results": roles 62 | } 63 | return flask.jsonify(resp) 64 | 65 | @app.route("/api/v1/roles//versions/") 66 | def get_versions(id): 67 | role = flask.current_app.roles.by_id( id ) 68 | if len(role) == 0 : 69 | galaxy_url = "https://galaxy.ansible.com%s" % flask.request.full_path 70 | return open_url(galaxy_url).read() 71 | versions = role[0]['summary_fields']['versions'] 72 | role_info = { 'summary_fields': { "role": { "id": role[0]['id'] , "name": role[0]['name'] } } } 73 | map( lambda d : role[0].set_url( flask.request.url_root , d ) , versions ) 74 | map( lambda d : d.update(role_info) , versions ) 75 | resp = { "count": len(versions), 76 | "cur_page": len(versions), 77 | "num_pages": len(versions), 78 | "next": None, 79 | "previous": None, 80 | "results": versions 81 | } 82 | return flask.jsonify(resp) 83 | 84 | @app.route("//.tar.gz") 85 | def download(rolename, roleversion): 86 | srcdir = os.path.join( flask.current_app.appconfig['roles_dir'] , rolename ) 87 | return flask.send_from_directory( srcdir , "%s.tar.gz" % roleversion ) 88 | 89 | @app.route("//", methods=['PUT']) 90 | def upload(rolename, roleversion): 91 | if flask.request.content_type != 'application/x-www-form-urlencoded' : 92 | return flask.jsonify({'msg': "Wrong content type '%s'" % flask.request.content_type.split(';')[0]}) , 405 93 | roledir = os.path.join( flask.current_app.appconfig['roles_dir'] , rolename ) 94 | matched_role = flask.current_app.roles.by_name( rolename ) 95 | if matched_role : 96 | matched_version = filter( lambda d : d['name'] == roleversion , matched_role[0]['summary_fields']['versions'] ) 97 | if matched_version : 98 | return flask.jsonify({'msg': "Role %s with version %s already exists" % ( rolename , roleversion ) }) , 409 99 | if not os.path.isdir( roledir ) : 100 | os.mkdir( roledir ) 101 | destination = os.path.join( roledir , "%s.tar.gz" % roleversion ) 102 | if os.path.isfile( destination ) : 103 | return flask.jsonify({'msg': "File for %s %s already exists" % ( rolename , roleversion ) }) , 409 104 | with open( destination , 'w' ) as fd : 105 | fd.write( flask.request.stream.read(flask.request.content_length) ) 106 | if matched_role : 107 | matched_role[0]['summary_fields']['versions'].append( { 'name':roleversion } ) 108 | else : 109 | _roles = iter([ application.galaxy_role( destination ) ]) 110 | flask.current_app.roles.add_roles( _roles ) 111 | return flask.jsonify({'msg': 'Done'}) , 201 112 | 113 | 114 | -------------------------------------------------------------------------------- /ansible_library/application.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (C) 2016 Javier Palacios 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # Version 2 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See full 11 | # GPLv2 for more details (http://www.gnu.org/licenses/gpl-2.0.html). 12 | 13 | import flask 14 | import tarfile 15 | import yaml 16 | import operator, itertools 17 | import time 18 | import os 19 | import glob 20 | import logging 21 | 22 | 23 | class abstract_role ( dict ) : 24 | 25 | def __init__ ( self , content={} ) : 26 | dict.__init__( self , content ) 27 | 28 | def set_url ( self , roles_dir , version ) : 29 | version['source'] = "" 30 | 31 | def expired ( self ) : 32 | False 33 | 34 | class role ( abstract_role ) : 35 | 36 | def __init__ ( self , id ) : 37 | abstract_role.__init__( self ) 38 | self['id'] = id 39 | 40 | def set_url ( self , root_url , version ) : 41 | version['source'] = "%s%s/%s.tar.gz" % ( root_url , self['name'] , version['name'] ) 42 | 43 | class galaxy_role ( abstract_role ) : 44 | 45 | def __init__ ( self , file_path ) : 46 | tar = tarfile.open( file_path ) 47 | meta = filter( lambda s : s.endswith('meta/main.yml') ,tar.getnames()) 48 | if len(meta) != 1: 49 | print "WARNING: '%s' is not an ansible role" % file_path 50 | return 51 | data = yaml.load( tar.extractfile(meta[0]) ) 52 | abstract_role.__init__( self , data['galaxy_info'] ) 53 | self['dependencies'] = data.get('dependencies', []) 54 | dir_name , file_name = os.path.split( file_path ) 55 | self['name'] = os.path.basename( dir_name ) 56 | self['version'] = file_name.rpartition('.tar')[0] 57 | 58 | 59 | class role_list ( list ) : 60 | 61 | def clear ( self ) : 62 | while self : 63 | self.pop() 64 | 65 | def add_roles ( self , role_list , next_id=None ) : 66 | if not next_id : 67 | next_id = self.next_id() 68 | _role = role( next_id ) 69 | _role.update( role_list.next() ) 70 | _role['versions'] = [ { 'name': str(_role.pop('version')) } ] 71 | for r in role_list : 72 | _role['versions'].append( { 'name': str(r.pop('version')) } ) 73 | _role['summary_fields'] = { 'dependencies': _role.pop('dependencies'), 74 | 'versions': _role.pop('versions') 75 | } 76 | self.append( _role ) 77 | 78 | def by_name ( self , name ) : 79 | return filter( lambda d : d['name'] == name , self ) 80 | 81 | def by_id ( self , id ) : 82 | return filter( lambda d : d['id'] == id , self ) 83 | 84 | def next_id ( self ) : 85 | ids = map( lambda x : x['id'] , self ) 86 | return 1 + max(ids) 87 | 88 | class proxied_role ( abstract_role ) : 89 | 90 | def __init__ ( self , galaxy_metadata , ttl ) : 91 | self.expiration = time.time() + ttl 92 | abstract_role.__init__( self , galaxy_metadata ) 93 | 94 | def expired ( self ) : 95 | time.time() > self.expiration 96 | 97 | 98 | class library ( flask.Flask ) : 99 | 100 | def __init__ ( self ) : 101 | flask.Flask.__init__( self , 'ansible-library' ) 102 | self.roles = role_list() 103 | self.galaxy = [] 104 | 105 | conffile = "/etc/ansible-library.yml" 106 | appconfig = { 'roles_dir': "/var/lib/galaxy", 107 | 'logfile': None, 108 | 'listen': "0.0.0.0", 109 | 'port': 3333, 110 | 'ttl': 3600, 111 | 'daemonize': False, 112 | 'piddir': None, 113 | 'debug': False 114 | } 115 | 116 | def run ( self , *args, **kwargs ) : 117 | if os.path.isfile( self.conffile ) : 118 | localconf = yaml.load( open( self.conffile ) ) 119 | if localconf : 120 | self.appconfig.update( localconf ) 121 | if self.appconfig['daemonize'] : 122 | if not self.appconfig['logfile'] : 123 | print "ERROR: daemonization requires a logfile" 124 | os.sys.exit(1) 125 | newpid = os.fork() 126 | if newpid : 127 | if self.appconfig['piddir'] : 128 | pidfile = os.path.join( self.appconfig['piddir'] , 'ansible-library.pid' ) 129 | with open( pidfile , 'w' ) as fd : 130 | fd.write( "%d\n" % newpid ) 131 | os.sys.exit() 132 | else : 133 | os.sys.stdout = open(os.devnull, 'w') 134 | os.sys.stderr = open(os.devnull, 'w') 135 | if self.appconfig['logfile'] : 136 | logger = logging.getLogger('werkzeug') 137 | logger.addHandler( logging.FileHandler( self.appconfig['logfile'] ) ) 138 | if self.appconfig['daemonize'] : 139 | os.sys.stdout = open(os.devnull, 'w') 140 | os.sys.stderr = open(os.devnull, 'w') 141 | self.load_roles() 142 | flask.Flask.run( self , host=self.appconfig['listen'], 143 | port=self.appconfig['port'], 144 | debug=self.appconfig['debug'] 145 | ) 146 | 147 | def load_roles ( self ) : 148 | _roles = [] 149 | for file_path in glob.glob( os.path.join( self.appconfig['roles_dir'] , "*/*.tar.gz" ) ) : 150 | _roles.append( galaxy_role( file_path ) ) 151 | 152 | _id = 1 153 | self.roles.clear() 154 | _roles = sorted( _roles , key=operator.itemgetter('name') ) 155 | for k, g in itertools.groupby(_roles, operator.itemgetter('name')): 156 | self.roles.add_roles( g , _id ) 157 | _id += 1 158 | 159 | 160 | -------------------------------------------------------------------------------- /ansible_library/tests.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (C) 2016 Javier Palacios 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # Version 2 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See full 11 | # GPLv2 for more details (http://www.gnu.org/licenses/gpl-2.0.html). 12 | 13 | import ansible_library 14 | import unittest 15 | import json 16 | import os 17 | import shutil 18 | import tempfile 19 | import yaml 20 | import tarfile 21 | 22 | from ansible.module_utils.urls import open_url 23 | 24 | 25 | class ansible_library_test ( unittest.TestCase ) : 26 | 27 | @classmethod 28 | def setUpClass ( cls ) : 29 | cls._tempdir = tempfile.mkdtemp() 30 | roles = yaml.load( open( 'tests/fixtures/roles.yml' ) ) 31 | for role in roles : 32 | role_name = role.pop('name') 33 | if not os.path.exists( "%s/%s" % ( cls._tempdir , role_name ) ) : 34 | os.mkdir( "%s/%s" % ( cls._tempdir , role_name ) ) 35 | file_name = "%s/%s.tar.gz" % ( role_name , role['galaxy_info']['version'] ) 36 | with open( "%s/meta.yml" % cls._tempdir , 'w' ) as fd : 37 | yaml.dump(role, fd, default_flow_style=False) 38 | with tarfile.open( "%s/%s" % ( cls._tempdir , file_name ) , mode="w:gz" ) as tar : 39 | tar.add( "%s/meta.yml" % cls._tempdir , "%s/meta/main.yml" % role_name ) 40 | os.unlink("%s/meta.yml" % cls._tempdir) 41 | 42 | @classmethod 43 | def tearDownClass ( cls ) : 44 | shutil.rmtree( cls._tempdir ) 45 | 46 | def unpack_roles ( self ) : 47 | roles = yaml.load( open( 'tests/fixtures/extraroles.yml' ) ) 48 | for role in roles : 49 | role_name = role.pop('name') 50 | if not os.path.exists( "%s/%s" % ( self._tempdir , role_name ) ) : 51 | os.mkdir( "%s/%s" % ( self._tempdir , role_name ) ) 52 | file_name = "%s/%s.tar.gz" % ( role_name , role['galaxy_info']['version'] ) 53 | with open( "%s/meta.yml" % self._tempdir , 'w' ) as fd : 54 | yaml.dump(role, fd, default_flow_style=False) 55 | with tarfile.open( "%s/%s" % ( self._tempdir , file_name ) , mode="w:gz" ) as tar : 56 | tar.add( "%s/meta.yml" % self._tempdir , "%s/meta/main.yml" % role_name ) 57 | os.unlink("%s/meta.yml" % self._tempdir) 58 | 59 | def setUp ( self ) : 60 | ansible_library.app.appconfig['roles_dir'] = self._tempdir 61 | ansible_library.app.load_roles() 62 | self.app = ansible_library.app.test_client() 63 | self.app.testing = True 64 | 65 | def get ( self , url ) : 66 | res = self.app.get( "/api/%s" % url ) 67 | self.assertEqual( res.status_code , 200 ) 68 | self.assertEqual( res.content_type , "application/json" ) 69 | return json.loads(res.data) 70 | 71 | def put ( self , url ) : 72 | res = self.app.put( "/api/%s" % url ) 73 | self.assertEqual( res.status_code , 200 ) 74 | self.assertEqual( res.content_type , "application/json" ) 75 | return json.loads(res.data) 76 | 77 | def test_api_version ( self ) : 78 | '''Verify API version''' 79 | data = self.get("") 80 | self.assertEqual( data['current_version'] , 'v1' ) 81 | 82 | galaxy = self.app.get( "https://galaxy.ansible.com/api/" ) 83 | galaxy = json.loads(galaxy.data) 84 | self.assertEqual( data['current_version'] , galaxy['current_version'] ) 85 | 86 | def test_local_role ( self ) : 87 | '''Get data for local role''' 88 | data = self.get( "v1/roles/?name=fever" ) 89 | self.assertEqual( data['count'] , 1 ) 90 | result = data['results'][0] 91 | self.assertEqual( result['name'] , "fever" ) 92 | 93 | def test_local_versions ( self ) : 94 | '''Get versions of local role''' 95 | data = self.get( "v1/roles/?name=fever" ) 96 | data = self.get( "v1/roles/%s/versions/" % data['results'][0]['id'] ) 97 | self.assertEqual( data['count'] , 2 ) 98 | versions = map( lambda v : v['name'] , data['results'] ) 99 | self.assertIn( "v1.0" , versions ) 100 | self.assertIn( "v1.1" , versions ) 101 | v11 = filter( lambda v : v['name'] == "v1.1" , data['results'] )[0] 102 | self.assertEqual( v11['source'] , "http://localhost/fever/v1.1.tar.gz" ) 103 | 104 | def test_download_local_role ( self ) : 105 | '''Download role tarball''' 106 | data = self.get( "v1/roles/1/versions/" ) 107 | self.assertNotEqual( data['results'][0]['source'] , "" ) 108 | role_tarball = self.app.get(data['results'][0]['source']) 109 | self.assertEqual( role_tarball.status_code , 200 ) 110 | 111 | def test_proxied_role ( self ) : 112 | '''Get public galaxy role if not a local one''' 113 | data = self.get( "v1/roles/?owner__username=Feverup&name=augeas" ) 114 | self.assertEqual( data['count'] , 1 ) 115 | result = data['results'][0] 116 | self.assertEqual( result['summary_fields']['namespace']['name'] , "Feverup" ) 117 | self.assertEqual( result['name'] , "augeas" ) 118 | 119 | galaxy = self.app.get( "https://galaxy.ansible.com/api/v1/roles/" , query_string="?owner__username=Feverup&name=augeas" ) 120 | galaxy = json.loads(galaxy.data) 121 | self.assertEqual( galaxy['count'] , 1 ) 122 | self.assertDictEqual( data['results'][0] , galaxy['results'][0] ) 123 | 124 | def test_proxied_versions ( self ) : 125 | '''Proxying versions from public galaxy''' 126 | data = self.get( "v1/roles/?owner__username=Feverup&name=augeas" ) 127 | data = self.get( "v1/roles/%s/versions/" % data['results'][0]['id'] ) 128 | 129 | galaxy = self.app.get( "https://galaxy.ansible.com/api/v1/roles/" , query_string="?owner__username=Feverup&name=augeas" ) 130 | galaxy = json.loads(galaxy.data) 131 | self.assertListEqual( data['results'] , galaxy['results'][0]['summary_fields']['versions'] ) 132 | 133 | def test_url_for_proxied_role ( self ) : 134 | '''Check url returned for proxied roles (regression javiplx/ansible-library#1)''' 135 | data = self.get( "v1/roles/?owner__username=Feverup&name=augeas" ) 136 | data = self.get( "v1/roles/%s/versions/" % data['results'][0]['id'] ) 137 | self.assertEqual( data['results'][0]['source'] , "" ) 138 | 139 | def test_version_field_class ( self ) : 140 | '''Ensure that versions are returned as strings (regression javiplx/ansible-library#8)''' 141 | data = self.get( "v1/roles/?name=djangoserver" ) 142 | data = self.get( "v1/roles/%s/versions/" % data['results'][0]['id'] ) 143 | self.assertEqual( data['count'] , 2 ) 144 | for d in data['results'] : 145 | self.assertIsInstance(d['name'], unicode) 146 | 147 | def test_roles_reload ( self ) : 148 | '''Reload roles from filesystem''' 149 | self.unpack_roles() 150 | 151 | data = self.put("reload") 152 | self.assertEqual( data['msg'] , 'Done' ) 153 | 154 | data = self.get( "v1/roles/?name=dnsmasq" ) 155 | self.assertEqual( data['count'] , 1 ) 156 | 157 | --------------------------------------------------------------------------------