@ -21,15 +21,16 @@
import datetime
import os
import subprocess
import tarfile
import tempfile
import yaml
from distutils . version import LooseVersion
from shutil import rmtree
from ansible import constants as C
from ansible . errors import AnsibleError
from ansible . module_utils . urls import open_url
from ansible . playbook . role . requirement import RoleRequirement
from ansible . galaxy . api import GalaxyAPI
try :
from __main__ import display
@ -51,6 +52,7 @@ class GalaxyRole(object):
self . _install_info = None
self . options = galaxy . options
self . galaxy = galaxy
self . name = name
self . version = version
@ -135,9 +137,9 @@ class GalaxyRole(object):
def remove ( self ) :
"""
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
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 self . metadata :
try :
@ -159,6 +161,7 @@ class GalaxyRole(object):
archive_url = ' https://github.com/ %s / %s /archive/ %s .tar.gz ' % ( role_data [ " github_user " ] , role_data [ " github_repo " ] , self . version )
else :
archive_url = self . src
display . display ( " - downloading role from %s " % archive_url )
try :
@ -170,87 +173,125 @@ class GalaxyRole(object):
data = url_file . read ( )
temp_file . close ( )
return temp_file . name
except :
# TODO: better urllib2 error handling for error
# messages that are more exact
display . error ( " failed to download the file. " )
except Exception as e :
display . error ( " failed to download the file: %s " % str ( e ) )
return False
def install ( self , role_filename ):
def install ( self ):
# the file is a tar, so open it that way and extract it
# to the specified (or default) roles directory
if not tarfile . is_tarfile ( role_filename ) :
display . error ( " the file downloaded was not a tar.gz " )
return False
else :
if role_filename . endswith ( ' .gz ' ) :
role_tar_file = tarfile . open ( role_filename , " r:gz " )
if self . scm :
# create tar file from scm url
tmp_file = RoleRequirement . scm_archive_role ( * * self . spec )
elif self . src :
if os . path . isfile ( self . src ) :
# installing a local tar.gz
tmp_file = self . src
elif ' :// ' in self . src :
role_data = self . src
tmp_file = self . fetch ( role_data )
else :
role_tar_file = tarfile . open ( role_filename , " r " )
# verify the role's meta file
meta_file = None
members = role_tar_file . getmembers ( )
# next find the metadata file
for member in members :
if self . META_MAIN in member . name :
meta_file = member
break
if not meta_file :
display . error ( " this role does not appear to have a meta/main.yml file. " )
return False
api = GalaxyAPI ( self . galaxy , self . options . api_server )
role_data = api . lookup_role_by_name ( self . src )
if not role_data :
raise AnsibleError ( " - sorry, %s was not found on %s . " % ( self . src , self . options . api_server ) )
role_versions = api . fetch_role_related ( ' versions ' , role_data [ ' id ' ] )
if not self . version :
# 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 ( )
self . version = str ( loose_versions [ - 1 ] )
else :
self . version = ' master '
elif self . version != ' master ' :
if role_versions and self . version not in [ a . get ( ' name ' , None ) for a in role_versions ] :
raise AnsibleError ( " - the specified version ( %s ) of %s was not found in the list of available versions ( %s ). " % ( self . version , self . name , role_versions ) )
tmp_file = self . fetch ( role_data )
else :
raise AnsibleError ( " No valid role data found " )
if tmp_file :
display . display ( " installing from %s " % tmp_file )
if not tarfile . is_tarfile ( tmp_file ) :
raise AnsibleError ( " the file downloaded was not a tar.gz " )
else :
if tmp_file . endswith ( ' .gz ' ) :
role_tar_file = tarfile . open ( tmp_file , " r:gz " )
else :
role_tar_file = tarfile . open ( tmp_file , " r " )
# verify the role's meta file
meta_file = None
members = role_tar_file . getmembers ( )
# next find the metadata file
for member in members :
if self . META_MAIN in member . name :
meta_file = member
break
if not meta_file :
raise AnsibleError ( " this role does not appear to have a meta/main.yml file. " )
else :
try :
self . _metadata = yaml . safe_load ( role_tar_file . extractfile ( meta_file ) )
except :
raise AnsibleError ( " this role does not appear to have a valid meta/main.yml file. " )
# 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
display . display ( " - extracting %s to %s " % ( self . name , self . path ) )
try :
self . _metadata = yaml . safe_load ( role_tar_file . extractfile ( meta_file ) )
except :
display . error ( " this role does not appear to have a valid meta/main.yml file. " )
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
display . display ( " - extracting %s to %s " % ( self . name , self . path ) )
try :
if os . path . exists ( self . path ) :
if not os . path . isdir ( self . path ) :
display . error ( " the specified roles path exists and is not a directory. " )
return False
elif not getattr ( self . options , " force " , False ) :
display . error ( " the specified role %s appears to already exist. Use --force to replace it. " % self . name )
return False
if os . path . exists ( self . path ) :
if not os . path . isdir ( self . path ) :
raise AnsibleError ( " the specified roles path exists and is not a directory. " )
elif not getattr ( self . options , " force " , False ) :
raise AnsibleError ( " the specified role %s appears to already exist. Use --force to replace it. " % self . name )
else :
# using --force, remove the old path
if not self . remove ( ) :
raise AnsibleError ( " %s doesn ' t appear to contain a role. \n please remove this directory manually if you really want to put the role here. " % self . path )
else :
# using --force, remove the old path
if not self . remove ( ) :
display . error ( " %s doesn ' t appear to contain a role. " % self . path )
display . error ( " please remove this directory manually if you really want to put the role here. " )
return False
else :
os . makedirs ( self . path )
os . makedirs ( self . path )
# now we do the actual extraction to the path
for member in members :
# 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
if member . isreg ( ) or member . issym ( ) :
parts = member . name . split ( os . sep ) [ 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 )
role_tar_file . extract ( member , self . path )
# write out the install info file for later use
self . _write_galaxy_install_info ( )
except OSError as e :
raise AnsibleError ( " Could not update files in %s : %s " % ( self . path , str ( e ) ) )
# return the parsed yaml metadata
display . display ( " - %s was installed successfully " % self . name )
try :
os . unlink ( tmp_file )
except ( OSError , IOError ) as e :
display . warning ( " Unable to remove tmp file ( %s ): %s " % ( tmp_file , str ( e ) ) )
return True
# now we do the actual extraction to the path
for member in members :
# 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
if member . isreg ( ) or member . issym ( ) :
parts = member . name . split ( os . sep ) [ 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 )
role_tar_file . extract ( member , self . path )
# write out the install info file for later use
self . _write_galaxy_install_info ( )
except OSError as e :
display . error ( " Could not update files in %s : %s " % ( self . path , str ( e ) ) )
return False
# return the parsed yaml metadata
display . display ( " - %s was installed successfully " % self . name )
return True
return False
@property
def spec ( self ) :
@ -266,65 +307,3 @@ class GalaxyRole(object):
return dict ( scm = self . scm , src = self . src , version = self . version , name = self . name )
@staticmethod
def url_to_spec ( roleurl ) :
# gets the role name out of a repo like
# http://git.example.com/repos/repo.git" => "repo"
if ' :// ' not in roleurl and ' @ ' not in roleurl :
return roleurl
trailing_path = roleurl . split ( ' / ' ) [ - 1 ]
if trailing_path . endswith ( ' .git ' ) :
trailing_path = trailing_path [ : - 4 ]
if trailing_path . endswith ( ' .tar.gz ' ) :
trailing_path = trailing_path [ : - 7 ]
if ' , ' in trailing_path :
trailing_path = trailing_path . split ( ' , ' ) [ 0 ]
return trailing_path
@staticmethod
def scm_archive_role ( scm , role_url , role_version , role_name ) :
if scm not in [ ' hg ' , ' git ' ] :
display . display ( " - scm %s is not currently supported " % scm )
return False
tempdir = tempfile . mkdtemp ( )
clone_cmd = [ scm , ' clone ' , role_url , role_name ]
with open ( ' /dev/null ' , ' w ' ) as devnull :
try :
display . display ( " - executing: %s " % " " . join ( clone_cmd ) )
popen = subprocess . Popen ( clone_cmd , cwd = tempdir , stdout = devnull , stderr = devnull )
except :
raise AnsibleError ( " error executing: %s " % " " . join ( clone_cmd ) )
rc = popen . wait ( )
if rc != 0 :
display . display ( " - command %s failed " % ' ' . join ( clone_cmd ) )
display . display ( " in directory %s " % tempdir )
return False
temp_file = tempfile . NamedTemporaryFile ( delete = False , suffix = ' .tar ' )
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 :
display . display ( " - executing: %s " % " " . join ( archive_cmd ) )
popen = subprocess . Popen ( archive_cmd , cwd = os . path . join ( tempdir , role_name ) ,
stderr = devnull , stdout = devnull )
rc = popen . wait ( )
if rc != 0 :
display . display ( " - command %s failed " % ' ' . join ( archive_cmd ) )
display . display ( " in directory %s " % tempdir )
return False
rmtree ( tempdir , ignore_errors = True )
return temp_file . name