Browse code

Merge pull request #12636 from bcoca/galaxy

Galaxy

James Cammarata authored on 2015/10/06 04:35:30
Showing 7 changed files
... ...
@@ -512,3 +512,16 @@ class CLI(object):
512 512
 
513 513
         return vault_pass
514 514
 
515
+    def get_opt(self, k, defval=""):
516
+        """
517
+        Returns an option from an Optparse values instance.
518
+        """
519
+        try:
520
+            data = getattr(self.options, k)
521
+        except:
522
+            return defval
523
+        if k == "roles_path":
524
+            if os.pathsep in data:
525
+                data = data.split(os.pathsep)[0]
526
+        return data
527
+
... ...
@@ -29,8 +29,6 @@ from distutils.version import LooseVersion
29 29
 from jinja2 import Environment
30 30
 
31 31
 import ansible.constants as C
32
-import ansible.utils
33
-import ansible.galaxy
34 32
 from ansible.cli import CLI
35 33
 from ansible.errors import AnsibleError, AnsibleOptionsError
36 34
 from ansible.galaxy import Galaxy
... ...
@@ -126,19 +124,6 @@ class GalaxyCLI(CLI):
126 126
 
127 127
         self.execute()
128 128
 
129
-    def get_opt(self, k, defval=""):
130
-        """
131
-        Returns an option from an Optparse values instance.
132
-        """
133
-        try:
134
-            data = getattr(self.options, k)
135
-        except:
136
-            return defval
137
-        if k == "roles_path":
138
-            if os.pathsep in data:
139
-                data = data.split(os.pathsep)[0]
140
-        return data
141
-
142 129
     def exit_without_ignore(self, rc=1):
143 130
         """
144 131
         Exits with the specified return code unless the
... ...
@@ -147,40 +132,6 @@ class GalaxyCLI(CLI):
147 147
         if not self.get_opt("ignore_errors", False):
148 148
             raise AnsibleError('- you can use --ignore-errors to skip failed roles and finish processing the list.')
149 149
 
150
-    def parse_requirements_files(self, role):
151
-        if 'role' in role:
152
-            # Old style: {role: "galaxy.role,version,name", other_vars: "here" }
153
-            role_info = role_spec_parse(role['role'])
154
-            if isinstance(role_info, dict):
155
-                # Warning: Slight change in behaviour here.  name may be being
156
-                # overloaded.  Previously, name was only a parameter to the role.
157
-                # Now it is both a parameter to the role and the name that
158
-                # ansible-galaxy will install under on the local system.
159
-                if 'name' in role and 'name' in role_info:
160
-                    del role_info['name']
161
-                role.update(role_info)
162
-        else:
163
-            # New style: { src: 'galaxy.role,version,name', other_vars: "here" }
164
-            if 'github.com' in role["src"] and 'http' in role["src"] and '+' not in role["src"] and not role["src"].endswith('.tar.gz'):
165
-                role["src"] = "git+" + role["src"]
166
-
167
-            if '+' in role["src"]:
168
-                (scm, src) = role["src"].split('+')
169
-                role["scm"] = scm
170
-                role["src"] = src
171
-
172
-            if 'name' not in role:
173
-                role["name"] = GalaxyRole.url_to_spec(role["src"])
174
-
175
-            if 'version' not in role:
176
-                role['version'] = ''
177
-
178
-            if 'scm' not in role:
179
-                role['scm'] = None
180
-
181
-        return role
182
-
183
-
184 150
     def _display_role_info(self, role_info):
185 151
 
186 152
         text = "\nRole: %s \n" % role_info['name']
... ...
@@ -298,9 +249,8 @@ class GalaxyCLI(CLI):
298 298
         data = ''
299 299
         for role in self.args:
300 300
 
301
-            role_info = {}
301
+            role_info = {'role_path': roles_path}
302 302
             gr = GalaxyRole(self.galaxy, role)
303
-            #self.galaxy.add_role(gr)
304 303
 
305 304
             install_info = gr.install_info
306 305
             if install_info:
... ...
@@ -351,118 +301,73 @@ class GalaxyCLI(CLI):
351 351
 
352 352
         no_deps    = self.get_opt("no_deps", False)
353 353
         force      = self.get_opt('force', False)
354
-        roles_path = self.get_opt("roles_path")
355 354
 
356
-        roles_done = []
357 355
         roles_left = []
358 356
         if role_file:
359
-            self.display.debug('Getting roles from %s' % role_file)
360 357
             try:
361
-                self.display.debug('Processing role file: %s' % role_file)
362 358
                 f = open(role_file, 'r')
363 359
                 if role_file.endswith('.yaml') or role_file.endswith('.yml'):
364
-                    try:
365
-                        rolesparsed = map(self.parse_requirements_files, yaml.safe_load(f))
366
-                    except Exception as e:
367
-                       raise AnsibleError("%s does not seem like a valid yaml file: %s" % (role_file, str(e)))
368
-                    roles_left = [GalaxyRole(self.galaxy, **r) for r in rolesparsed]
360
+                    for role in yaml.safe_load(f.read()):
361
+                        self.display.debug('found role %s in yaml file' % str(role))
362
+                        if 'name' not in role:
363
+                            if 'src' in role:
364
+                                role['name'] = RoleRequirement.repo_url_to_role_name(role['src'])
365
+                            else:
366
+                                raise AnsibleError("Must specify name or src for role")
367
+                        roles_left.append(GalaxyRole(self.galaxy, **role))
369 368
                 else:
369
+                    self.display.deprecated("going forward only the yaml format will be supported")
370 370
                     # roles listed in a file, one per line
371
-                    self.display.deprecated("Non yaml files for role requirements")
372
-                    for rname in f.readlines():
373
-                        if rname.startswith("#") or rname.strip() == '':
374
-                            continue
375
-                        roles_left.append(GalaxyRole(self.galaxy, rname.strip()))
371
+                    for rline in f.readlines():
372
+                        self.display.debug('found role %s in text file' % str(rline))
373
+                        roles_left.append(GalaxyRole(self.galaxy, **RoleRequirement.role_spec_parse(rline)))
376 374
                 f.close()
377
-            except (IOError,OSError) as e:
378
-                raise AnsibleError("Unable to read requirements file (%s): %s" % (role_file, str(e)))
375
+            except (IOError, OSError) as e:
376
+                self.display.error('Unable to open %s: %s' % (role_file, str(e)))
379 377
         else:
380 378
             # roles were specified directly, so we'll just go out grab them
381 379
             # (and their dependencies, unless the user doesn't want us to).
382 380
             for rname in self.args:
383 381
                 roles_left.append(GalaxyRole(self.galaxy, rname.strip()))
384 382
 
385
-        while len(roles_left) > 0:
383
+        for role in roles_left:
384
+            self.display.debug('Installing role %s ' % role.name)
386 385
             # query the galaxy API for the role data
387 386
             role_data = None
388 387
             role = roles_left.pop(0)
389
-            role_path = role.path
390 388
 
391 389
             if role.install_info is not None and not force:
392 390
                 self.display.display('- %s is already installed, skipping.' % role.name)
393 391
                 continue
394 392
 
395
-            if role_path:
396
-                self.options.roles_path = role_path
397
-            else:
398
-                self.options.roles_path = roles_path
399
-
400
-            self.display.debug('Installing role %s from %s' % (role.name, self.options.roles_path))
393
+            try:
394
+                installed = role.install()
395
+            except AnsibleError as e:
396
+                self.display.warning("- %s was NOT installed successfully: %s " % (role.name, str(e)))
397
+                self.exit_without_ignore()
398
+                continue
401 399
 
402
-            tmp_file = None
403
-            installed = False
404
-            if role.src and os.path.isfile(role.src):
405
-                # installing a local tar.gz
406
-                tmp_file = role.src
407
-            else:
408
-                if role.scm:
409
-                    # create tar file from scm url
410
-                    tmp_file = GalaxyRole.scm_archive_role(role.scm, role.src, role.version, role.name)
411
-                if role.src:
412
-                    if '://' not in role.src:
413
-                        role_data = self.api.lookup_role_by_name(role.src)
414
-                        if not role_data:
415
-                            self.display.warning("- sorry, %s was not found on %s." % (role.src, self.options.api_server))
416
-                            self.exit_without_ignore()
417
-                            continue
418
-
419
-                        role_versions = self.api.fetch_role_related('versions', role_data['id'])
420
-                        if not role.version:
421
-                            # convert the version names to LooseVersion objects
422
-                            # and sort them to get the latest version. If there
423
-                            # are no versions in the list, we'll grab the head
424
-                            # of the master branch
425
-                            if len(role_versions) > 0:
426
-                                loose_versions = [LooseVersion(a.get('name',None)) for a in role_versions]
427
-                                loose_versions.sort()
428
-                                role.version = str(loose_versions[-1])
429
-                            else:
430
-                                role.version = 'master'
431
-                        elif role.version != 'master':
432
-                            if role_versions and role.version not in [a.get('name', None) for a in role_versions]:
433
-                                self.display.warning('role is %s' % role)
434
-                                self.display.warning("- the specified version (%s) was not found in the list of available versions (%s)." % (role.version, role_versions))
435
-                                self.exit_without_ignore()
436
-                                continue
437
-
438
-                    # download the role. if --no-deps was specified, we stop here,
439
-                    # otherwise we recursively grab roles and all of their deps.
440
-                    tmp_file = role.fetch(role_data)
441
-            if tmp_file:
442
-                installed = role.install(tmp_file)
443
-                # we're done with the temp file, clean it up
444
-                if tmp_file != role.src:
445
-                    os.unlink(tmp_file)
446
-                # install dependencies, if we want them
447
-                if not no_deps and installed:
448
-                    role_dependencies = role.metadata.get('dependencies', [])
449
-                    for dep in role_dependencies:
450
-                        self.display.debug('Installing dep %s' % dep)
451
-                        dep_req = RoleRequirement()
452
-                        __, dep_name, __ = dep_req.parse(dep)
453
-                        dep_role = GalaxyRole(self.galaxy, name=dep_name)
454
-                        if dep_role.install_info is None or force:
455
-                            if dep_role not in roles_left:
456
-                                self.display.display('- adding dependency: %s' % dep_name)
457
-                                roles_left.append(GalaxyRole(self.galaxy, name=dep_name))
458
-                            else:
459
-                                self.display.display('- dependency %s already pending installation.' % dep_name)
400
+            # install dependencies, if we want them
401
+            if not no_deps and installed:
402
+                role_dependencies = role.metadata.get('dependencies', [])
403
+                for dep in role_dependencies:
404
+                    self.display.debug('Installing dep %s' % dep)
405
+                    dep_req = RoleRequirement()
406
+                    __, dep_name, __ = dep_req.parse(dep)
407
+                    dep_role = GalaxyRole(self.galaxy, name=dep_name)
408
+                    if dep_role.install_info is None or force:
409
+                        if dep_role not in roles_left:
410
+                            self.display.display('- adding dependency: %s' % dep_name)
411
+                            roles_left.append(GalaxyRole(self.galaxy, name=dep_name))
460 412
                         else:
461
-                            self.display.display('- dependency %s is already installed, skipping.' % dep_name)
413
+                            self.display.display('- dependency %s already pending installation.' % dep_name)
414
+                    else:
415
+                        self.display.display('- dependency %s is already installed, skipping.' % dep_name)
462 416
 
463
-            if not tmp_file or not installed:
417
+            if not installed:
464 418
                 self.display.warning("- %s was NOT installed successfully." % role.name)
465 419
                 self.exit_without_ignore()
420
+
466 421
         return 0
467 422
 
468 423
     def execute_remove(self):
... ...
@@ -21,15 +21,16 @@
21 21
 
22 22
 import datetime
23 23
 import os
24
-import subprocess
25 24
 import tarfile
26 25
 import tempfile
27 26
 import yaml
27
+from distutils.version import LooseVersion
28 28
 from shutil import rmtree
29 29
 
30
-from ansible import constants as C
31 30
 from ansible.errors import AnsibleError
32 31
 from ansible.module_utils.urls import open_url
32
+from ansible.playbook.role.requirement import RoleRequirement
33
+from ansible.galaxy.api import GalaxyAPI
33 34
 
34 35
 try:
35 36
     from __main__ import display
... ...
@@ -51,6 +52,7 @@ class GalaxyRole(object):
51 51
         self._install_info = None
52 52
 
53 53
         self.options = galaxy.options
54
+        self.galaxy  = galaxy
54 55
 
55 56
         self.name = name
56 57
         self.version = version
... ...
@@ -135,9 +137,9 @@ class GalaxyRole(object):
135 135
 
136 136
     def remove(self):
137 137
         """
138
-        Removes the specified role from the roles path. There is a
139
-        sanity check to make sure there's a meta/main.yml file at this
140
-        path so the user doesn't blow away random directories
138
+        Removes the specified role from the roles path.
139
+        There is a sanity check to make sure there's a meta/main.yml file at this
140
+        path so the user doesn't blow away random directories.
141 141
         """
142 142
         if self.metadata:
143 143
             try:
... ...
@@ -159,6 +161,7 @@ class GalaxyRole(object):
159 159
                 archive_url = 'https://github.com/%s/%s/archive/%s.tar.gz' % (role_data["github_user"], role_data["github_repo"], self.version)
160 160
             else:
161 161
                 archive_url = self.src
162
+
162 163
             display.display("- downloading role from %s" % archive_url)
163 164
 
164 165
             try:
... ...
@@ -170,87 +173,125 @@ class GalaxyRole(object):
170 170
                     data = url_file.read()
171 171
                 temp_file.close()
172 172
                 return temp_file.name
173
-            except:
174
-                # TODO: better urllib2 error handling for error
175
-                #       messages that are more exact
176
-                display.error("failed to download the file.")
173
+            except Exception as e:
174
+                display.error("failed to download the file: %s" % str(e))
177 175
 
178 176
         return False
179 177
 
180
-    def install(self, role_filename):
178
+    def install(self):
181 179
         # the file is a tar, so open it that way and extract it
182 180
         # to the specified (or default) roles directory
183 181
 
184
-        if not tarfile.is_tarfile(role_filename):
185
-            display.error("the file downloaded was not a tar.gz")
186
-            return False
187
-        else:
188
-            if role_filename.endswith('.gz'):
189
-                role_tar_file = tarfile.open(role_filename, "r:gz")
182
+        if self.scm:
183
+            # create tar file from scm url
184
+            tmp_file = RoleRequirement.scm_archive_role(**self.spec)
185
+        elif self.src:
186
+            if  os.path.isfile(self.src):
187
+                # installing a local tar.gz
188
+                tmp_file = self.src
189
+            elif '://' in self.src:
190
+                role_data = self.src
191
+                tmp_file = self.fetch(role_data)
190 192
             else:
191
-                role_tar_file = tarfile.open(role_filename, "r")
192
-            # verify the role's meta file
193
-            meta_file = None
194
-            members = role_tar_file.getmembers()
195
-            # next find the metadata file
196
-            for member in members:
197
-                if self.META_MAIN in member.name:
198
-                    meta_file = member
199
-                    break
200
-            if not meta_file:
201
-                display.error("this role does not appear to have a meta/main.yml file.")
202
-                return False
193
+                api = GalaxyAPI(self.galaxy, self.options.api_server)
194
+                role_data = api.lookup_role_by_name(self.src)
195
+                if not role_data:
196
+                    raise AnsibleError("- sorry, %s was not found on %s." % (self.src, self.options.api_server))
197
+
198
+                role_versions = api.fetch_role_related('versions', role_data['id'])
199
+                if not self.version:
200
+                    # convert the version names to LooseVersion objects
201
+                    # and sort them to get the latest version. If there
202
+                    # are no versions in the list, we'll grab the head
203
+                    # of the master branch
204
+                    if len(role_versions) > 0:
205
+                        loose_versions = [LooseVersion(a.get('name',None)) for a in role_versions]
206
+                        loose_versions.sort()
207
+                        self.version = str(loose_versions[-1])
208
+                    else:
209
+                        self.version = 'master'
210
+                elif self.version != 'master':
211
+                    if role_versions and self.version not in [a.get('name', None) for a in role_versions]:
212
+                        raise AnsibleError("- the specified version (%s) of %s was not found in the list of available versions (%s)." % (self.version, self.name, role_versions))
213
+
214
+                tmp_file = self.fetch(role_data)
215
+
216
+        else:
217
+           raise AnsibleError("No valid role data found")
218
+
219
+
220
+        if tmp_file:
221
+
222
+            display.display("installing from %s" % tmp_file)
223
+
224
+            if not tarfile.is_tarfile(tmp_file):
225
+                raise AnsibleError("the file downloaded was not a tar.gz")
203 226
             else:
227
+                if tmp_file.endswith('.gz'):
228
+                    role_tar_file = tarfile.open(tmp_file, "r:gz")
229
+                else:
230
+                    role_tar_file = tarfile.open(tmp_file, "r")
231
+                # verify the role's meta file
232
+                meta_file = None
233
+                members = role_tar_file.getmembers()
234
+                # next find the metadata file
235
+                for member in members:
236
+                    if self.META_MAIN in member.name:
237
+                        meta_file = member
238
+                        break
239
+                if not meta_file:
240
+                    raise AnsibleError("this role does not appear to have a meta/main.yml file.")
241
+                else:
242
+                    try:
243
+                        self._metadata = yaml.safe_load(role_tar_file.extractfile(meta_file))
244
+                    except:
245
+                        raise AnsibleError("this role does not appear to have a valid meta/main.yml file.")
246
+
247
+                # we strip off the top-level directory for all of the files contained within
248
+                # the tar file here, since the default is 'github_repo-target', and change it
249
+                # to the specified role's name
250
+                display.display("- extracting %s to %s" % (self.name, self.path))
204 251
                 try:
205
-                    self._metadata = yaml.safe_load(role_tar_file.extractfile(meta_file))
206
-                except:
207
-                    display.error("this role does not appear to have a valid meta/main.yml file.")
208
-                    return False
209
-
210
-            # we strip off the top-level directory for all of the files contained within
211
-            # the tar file here, since the default is 'github_repo-target', and change it
212
-            # to the specified role's name
213
-            display.display("- extracting %s to %s" % (self.name, self.path))
214
-            try:
215
-                if os.path.exists(self.path):
216
-                    if not os.path.isdir(self.path):
217
-                        display.error("the specified roles path exists and is not a directory.")
218
-                        return False
219
-                    elif not getattr(self.options, "force", False):
220
-                        display.error("the specified role %s appears to already exist. Use --force to replace it." % self.name)
221
-                        return False
252
+                    if os.path.exists(self.path):
253
+                        if not os.path.isdir(self.path):
254
+                            raise AnsibleError("the specified roles path exists and is not a directory.")
255
+                        elif not getattr(self.options, "force", False):
256
+                            raise AnsibleError("the specified role %s appears to already exist. Use --force to replace it." % self.name)
257
+                        else:
258
+                            # using --force, remove the old path
259
+                            if not self.remove():
260
+                                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)
222 261
                     else:
223
-                        # using --force, remove the old path
224
-                        if not self.remove():
225
-                            display.error("%s doesn't appear to contain a role." % self.path)
226
-                            display.error("  please remove this directory manually if you really want to put the role here.")
227
-                            return False
228
-                else:
229
-                    os.makedirs(self.path)
262
+                        os.makedirs(self.path)
263
+
264
+                    # now we do the actual extraction to the path
265
+                    for member in members:
266
+                        # we only extract files, and remove any relative path
267
+                        # bits that might be in the file for security purposes
268
+                        # and drop the leading directory, as mentioned above
269
+                        if member.isreg() or member.issym():
270
+                            parts = member.name.split(os.sep)[1:]
271
+                            final_parts = []
272
+                            for part in parts:
273
+                                if part != '..' and '~' not in part and '$' not in part:
274
+                                    final_parts.append(part)
275
+                            member.name = os.path.join(*final_parts)
276
+                            role_tar_file.extract(member, self.path)
277
+
278
+                    # write out the install info file for later use
279
+                    self._write_galaxy_install_info()
280
+                except OSError as e:
281
+                   raise AnsibleError("Could not update files in %s: %s" % (self.path, str(e)))
282
+
283
+                # return the parsed yaml metadata
284
+                display.display("- %s was installed successfully" % self.name)
285
+                try:
286
+                    os.unlink(tmp_file)
287
+                except (OSError,IOError) as e:
288
+                    display.warning("Unable to remove tmp file (%s): %s" % (tmp_file, str(e)))
289
+                return True
230 290
 
231
-                # now we do the actual extraction to the path
232
-                for member in members:
233
-                    # we only extract files, and remove any relative path
234
-                    # bits that might be in the file for security purposes
235
-                    # and drop the leading directory, as mentioned above
236
-                    if member.isreg() or member.issym():
237
-                        parts = member.name.split(os.sep)[1:]
238
-                        final_parts = []
239
-                        for part in parts:
240
-                            if part != '..' and '~' not in part and '$' not in part:
241
-                                final_parts.append(part)
242
-                        member.name = os.path.join(*final_parts)
243
-                        role_tar_file.extract(member, self.path)
244
-
245
-                # write out the install info file for later use
246
-                self._write_galaxy_install_info()
247
-            except OSError as e:
248
-                display.error("Could not update files in %s: %s" % (self.path, str(e)))
249
-                return False
250
-
251
-            # return the parsed yaml metadata
252
-            display.display("- %s was installed successfully" % self.name)
253
-            return True
291
+        return False
254 292
 
255 293
     @property
256 294
     def spec(self):
... ...
@@ -266,65 +307,3 @@ class GalaxyRole(object):
266 266
         return dict(scm=self.scm, src=self.src, version=self.version, name=self.name)
267 267
 
268 268
 
269
-    @staticmethod
270
-    def url_to_spec(roleurl):
271
-        # gets the role name out of a repo like
272
-        # http://git.example.com/repos/repo.git" => "repo"
273
-
274
-        if '://' not in roleurl and '@' not in roleurl:
275
-            return roleurl
276
-        trailing_path = roleurl.split('/')[-1]
277
-        if trailing_path.endswith('.git'):
278
-            trailing_path = trailing_path[:-4]
279
-        if trailing_path.endswith('.tar.gz'):
280
-            trailing_path = trailing_path[:-7]
281
-        if ',' in trailing_path:
282
-            trailing_path = trailing_path.split(',')[0]
283
-        return trailing_path
284
-
285
-    @staticmethod
286
-    def scm_archive_role(scm, role_url, role_version, role_name):
287
-        if scm not in ['hg', 'git']:
288
-            display.display("- scm %s is not currently supported" % scm)
289
-            return False
290
-        tempdir = tempfile.mkdtemp()
291
-        clone_cmd = [scm, 'clone', role_url, role_name]
292
-        with open('/dev/null', 'w') as devnull:
293
-            try:
294
-                display.display("- executing: %s" % " ".join(clone_cmd))
295
-                popen = subprocess.Popen(clone_cmd, cwd=tempdir, stdout=devnull, stderr=devnull)
296
-            except:
297
-                raise AnsibleError("error executing: %s" % " ".join(clone_cmd))
298
-            rc = popen.wait()
299
-        if rc != 0:
300
-            display.display("- command %s failed" % ' '.join(clone_cmd))
301
-            display.display("  in directory %s" % tempdir)
302
-            return False
303
-
304
-        temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.tar')
305
-        if scm == 'hg':
306
-            archive_cmd = ['hg', 'archive', '--prefix', "%s/" % role_name]
307
-            if role_version:
308
-                archive_cmd.extend(['-r', role_version])
309
-            archive_cmd.append(temp_file.name)
310
-        if scm == 'git':
311
-            archive_cmd = ['git', 'archive', '--prefix=%s/' % role_name, '--output=%s' % temp_file.name]
312
-            if role_version:
313
-                archive_cmd.append(role_version)
314
-            else:
315
-                archive_cmd.append('HEAD')
316
-
317
-        with open('/dev/null', 'w') as devnull:
318
-            display.display("- executing: %s" % " ".join(archive_cmd))
319
-            popen = subprocess.Popen(archive_cmd, cwd=os.path.join(tempdir, role_name),
320
-                                     stderr=devnull, stdout=devnull)
321
-            rc = popen.wait()
322
-        if rc != 0:
323
-            display.display("- command %s failed" % ' '.join(archive_cmd))
324
-            display.display("  in directory %s" % tempdir)
325
-            return False
326
-
327
-        rmtree(tempdir, ignore_errors=True)
328
-
329
-        return temp_file.name
330
-
... ...
@@ -21,19 +21,14 @@ __metaclass__ = type
21 21
 
22 22
 from six import iteritems, string_types
23 23
 
24
-import inspect
25 24
 import os
26 25
 
27
-from hashlib import sha1
28
-
29 26
 from ansible.errors import AnsibleError, AnsibleParserError
30
-from ansible.parsing import DataLoader
31 27
 from ansible.playbook.attribute import FieldAttribute
32 28
 from ansible.playbook.base import Base
33 29
 from ansible.playbook.become import Become
34 30
 from ansible.playbook.conditional import Conditional
35 31
 from ansible.playbook.helpers import load_list_of_blocks
36
-from ansible.playbook.role.include import RoleInclude
37 32
 from ansible.playbook.role.metadata import RoleMetadata
38 33
 from ansible.playbook.taggable import Taggable
39 34
 from ansible.plugins import get_all_plugin_loaders
... ...
@@ -19,11 +19,14 @@
19 19
 from __future__ import (absolute_import, division, print_function)
20 20
 __metaclass__ = type
21 21
 
22
-from six import iteritems, string_types
22
+from six import string_types
23 23
 
24 24
 import os
25
+import shutil
26
+import subprocess
27
+import tempfile
25 28
 
26
-from ansible.errors import AnsibleError, AnsibleParserError
29
+from ansible.errors import AnsibleError
27 30
 from ansible.playbook.role.definition import RoleDefinition
28 31
 
29 32
 __all__ = ['RoleRequirement']
... ...
@@ -73,7 +76,7 @@ class RoleRequirement(RoleDefinition):
73 73
     def _preprocess_role_spec(self, ds):
74 74
         if 'role' in ds:
75 75
             # Old style: {role: "galaxy.role,version,name", other_vars: "here" }
76
-            role_info = role_spec_parse(ds['role'])
76
+            role_info = RoleRequirement.role_spec_parse(ds['role'])
77 77
             if isinstance(role_info, dict):
78 78
                 # Warning: Slight change in behaviour here.  name may be being
79 79
                 # overloaded.  Previously, name was only a parameter to the role.
... ...
@@ -96,7 +99,7 @@ class RoleRequirement(RoleDefinition):
96 96
                 ds["role"] = ds["name"]
97 97
                 del ds["name"]
98 98
             else:
99
-                ds["role"] = repo_url_to_role_name(ds["src"])
99
+                ds["role"] = RoleRequirement.repo_url_to_role_name(ds["src"])
100 100
 
101 101
             # set some values to a default value, if none were specified
102 102
             ds.setdefault('version', '')
... ...
@@ -104,102 +107,137 @@ class RoleRequirement(RoleDefinition):
104 104
 
105 105
         return ds
106 106
 
107
-def repo_url_to_role_name(repo_url):
108
-    # gets the role name out of a repo like
109
-    # http://git.example.com/repos/repo.git" => "repo"
110
-
111
-    if '://' not in repo_url and '@' not in repo_url:
112
-        return repo_url
113
-    trailing_path = repo_url.split('/')[-1]
114
-    if trailing_path.endswith('.git'):
115
-        trailing_path = trailing_path[:-4]
116
-    if trailing_path.endswith('.tar.gz'):
117
-        trailing_path = trailing_path[:-7]
118
-    if ',' in trailing_path:
119
-        trailing_path = trailing_path.split(',')[0]
120
-    return trailing_path
121
-
122
-def role_spec_parse(role_spec):
123
-    # takes a repo and a version like
124
-    # git+http://git.example.com/repos/repo.git,v1.0
125
-    # and returns a list of properties such as:
126
-    # {
127
-    #   'scm': 'git',
128
-    #   'src': 'http://git.example.com/repos/repo.git',
129
-    #   'version': 'v1.0',
130
-    #   'name': 'repo'
131
-    # }
132
-
133
-    default_role_versions = dict(git='master', hg='tip')
134
-
135
-    role_spec = role_spec.strip()
136
-    role_version = ''
137
-    if role_spec == "" or role_spec.startswith("#"):
138
-        return (None, None, None, None)
139
-
140
-    tokens = [s.strip() for s in role_spec.split(',')]
141
-
142
-    # assume https://github.com URLs are git+https:// URLs and not
143
-    # tarballs unless they end in '.zip'
144
-    if 'github.com/' in tokens[0] and not tokens[0].startswith("git+") and not tokens[0].endswith('.tar.gz'):
145
-        tokens[0] = 'git+' + tokens[0]
146
-
147
-    if '+' in tokens[0]:
148
-        (scm, role_url) = tokens[0].split('+')
149
-    else:
150
-        scm = None
151
-        role_url = tokens[0]
152
-
153
-    if len(tokens) >= 2:
154
-        role_version = tokens[1]
155
-
156
-    if len(tokens) == 3:
157
-        role_name = tokens[2]
158
-    else:
159
-        role_name = repo_url_to_role_name(tokens[0])
160
-
161
-    if scm and not role_version:
162
-        role_version = default_role_versions.get(scm, '')
163
-
164
-    return dict(scm=scm, src=role_url, version=role_version, role_name=role_name)
165
-
166
-# FIXME: all of these methods need to be cleaned up/reorganized below this
167
-def get_opt(options, k, defval=""):
168
-    """
169
-    Returns an option from an Optparse values instance.
170
-    """
171
-    try:
172
-        data = getattr(options, k)
173
-    except:
174
-        return defval
175
-    if k == "roles_path":
176
-        if os.pathsep in data:
177
-            data = data.split(os.pathsep)[0]
178
-    return data
179
-
180
-def get_role_path(role_name, options):
181
-    """
182
-    Returns the role path based on the roles_path option
183
-    and the role name.
184
-    """
185
-    roles_path = get_opt(options,'roles_path')
186
-    roles_path = os.path.join(roles_path, role_name)
187
-    roles_path = os.path.expanduser(roles_path)
188
-    return roles_path
107
+    @staticmethod
108
+    def repo_url_to_role_name(repo_url):
109
+        # gets the role name out of a repo like
110
+        # http://git.example.com/repos/repo.git" => "repo"
111
+
112
+        if '://' not in repo_url and '@' not in repo_url:
113
+            return repo_url
114
+        trailing_path = repo_url.split('/')[-1]
115
+        if trailing_path.endswith('.git'):
116
+            trailing_path = trailing_path[:-4]
117
+        if trailing_path.endswith('.tar.gz'):
118
+            trailing_path = trailing_path[:-7]
119
+        if ',' in trailing_path:
120
+            trailing_path = trailing_path.split(',')[0]
121
+        return trailing_path
122
+
123
+    @staticmethod
124
+    def role_spec_parse(role_spec):
125
+        # takes a repo and a version like
126
+        # git+http://git.example.com/repos/repo.git,v1.0
127
+        # and returns a list of properties such as:
128
+        # {
129
+        #   'scm': 'git',
130
+        #   'src': 'http://git.example.com/repos/repo.git',
131
+        #   'version': 'v1.0',
132
+        #   'name': 'repo'
133
+        # }
134
+
135
+        default_role_versions = dict(git='master', hg='tip')
136
+
137
+        role_spec = role_spec.strip()
138
+        role_version = ''
139
+        if role_spec == "" or role_spec.startswith("#"):
140
+            return (None, None, None, None)
141
+
142
+        tokens = [s.strip() for s in role_spec.split(',')]
143
+
144
+        # assume https://github.com URLs are git+https:// URLs and not
145
+        # tarballs unless they end in '.zip'
146
+        if 'github.com/' in tokens[0] and not tokens[0].startswith("git+") and not tokens[0].endswith('.tar.gz'):
147
+            tokens[0] = 'git+' + tokens[0]
148
+
149
+        if '+' in tokens[0]:
150
+            (scm, role_url) = tokens[0].split('+')
151
+        else:
152
+            scm = None
153
+            role_url = tokens[0]
189 154
 
190
-def get_role_metadata(role_name, options):
191
-    """
192
-    Returns the metadata as YAML, if the file 'meta/main.yml'
193
-    exists in the specified role_path
194
-    """
195
-    role_path = os.path.join(get_role_path(role_name, options), 'meta/main.yml')
196
-    try:
197
-        if os.path.isfile(role_path):
198
-            f = open(role_path, 'r')
199
-            meta_data = yaml.safe_load(f)
200
-            f.close()
201
-            return meta_data
155
+        if len(tokens) >= 2:
156
+            role_version = tokens[1]
157
+
158
+        if len(tokens) == 3:
159
+            role_name = tokens[2]
202 160
         else:
203
-            return None
204
-    except:
205
-        return None    
161
+            role_name = RoleRequirement.repo_url_to_role_name(tokens[0])
162
+
163
+        if scm and not role_version:
164
+            role_version = default_role_versions.get(scm, '')
165
+
166
+        return dict(scm=scm, src=role_url, version=role_version, name=role_name)
167
+
168
+    @staticmethod
169
+    def role_yaml_parse(role):
170
+
171
+        if 'role' in role:
172
+            # Old style: {role: "galaxy.role,version,name", other_vars: "here" }
173
+            role_info = RoleRequirement.role_spec_parse(role['role'])
174
+            if isinstance(role_info, dict):
175
+                # Warning: Slight change in behaviour here.  name may be being
176
+                # overloaded.  Previously, name was only a parameter to the role.
177
+                # Now it is both a parameter to the role and the name that
178
+                # ansible-galaxy will install under on the local system.
179
+                if 'name' in role and 'name' in role_info:
180
+                    del role_info['name']
181
+                role.update(role_info)
182
+        else:
183
+            # New style: { src: 'galaxy.role,version,name', other_vars: "here" }
184
+            if 'github.com' in role["src"] and 'http' in role["src"] and '+' not in role["src"] and not role["src"].endswith('.tar.gz'):
185
+                role["src"] = "git+" + role["src"]
186
+
187
+            if '+' in role["src"]:
188
+                (scm, src) = role["src"].split('+')
189
+                role["scm"] = scm
190
+                role["src"] = src
191
+
192
+            if 'name' not in role:
193
+                role["name"] = RoleRequirement.repo_url_to_role_name(role["src"])
194
+
195
+            if 'version' not in role:
196
+                role['version'] = ''
197
+
198
+            if 'scm' not in role:
199
+                role['scm'] = None
200
+
201
+        return role
202
+
203
+    @staticmethod
204
+    def scm_archive_role(src, scm='git', name=None, version='HEAD'):
205
+        if scm not in ['hg', 'git']:
206
+            raise AnsibleError("- scm %s is not currently supported" % scm)
207
+        tempdir = tempfile.mkdtemp()
208
+        clone_cmd = [scm, 'clone', src, name]
209
+        with open('/dev/null', 'w') as devnull:
210
+            try:
211
+                popen = subprocess.Popen(clone_cmd, cwd=tempdir, stdout=devnull, stderr=devnull)
212
+            except:
213
+                raise AnsibleError("error executing: %s" % " ".join(clone_cmd))
214
+            rc = popen.wait()
215
+        if rc != 0:
216
+            raise AnsibleError ("- command %s failed in directory %s" % (' '.join(clone_cmd), tempdir))
217
+
218
+        temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.tar')
219
+        if scm == 'hg':
220
+            archive_cmd = ['hg', 'archive', '--prefix', "%s/" % name]
221
+            if version:
222
+                archive_cmd.extend(['-r', version])
223
+            archive_cmd.append(temp_file.name)
224
+        if scm == 'git':
225
+            archive_cmd = ['git', 'archive', '--prefix=%s/' % name, '--output=%s' % temp_file.name]
226
+            if version:
227
+                archive_cmd.append(version)
228
+            else:
229
+                archive_cmd.append('HEAD')
230
+
231
+        with open('/dev/null', 'w') as devnull:
232
+            popen = subprocess.Popen(archive_cmd, cwd=os.path.join(tempdir, name),
233
+                                     stderr=devnull, stdout=devnull)
234
+            rc = popen.wait()
235
+        if rc != 0:
236
+            raise AnsibleError("- command %s failed in directory %s" % (' '.join(archive_cmd), tempdir))
237
+
238
+        shutil.rmtree(tempdir, ignore_errors=True)
239
+        return temp_file.name
240
+
... ...
@@ -63,6 +63,7 @@ def get_docstring(filename, verbose=False):
63 63
                         theid = t.id
64 64
                     except AttributeError as e:
65 65
                         # skip errors can happen when trying to use the normal code
66
+                        display.warning("Failed to assign id for %t on %s, skipping" % (t, filename))
66 67
                         continue
67 68
 
68 69
                     if 'DOCUMENTATION' in theid:
... ...
@@ -119,6 +120,7 @@ def get_docstring(filename, verbose=False):
119 119
     except:
120 120
         display.error("unable to parse %s" % filename)
121 121
         if verbose == True:
122
+            display.display("unable to parse %s" % filename)
122 123
             raise
123 124
     return doc, plainexamples, returndocs
124 125
 
... ...
@@ -172,7 +172,7 @@ test_galaxy: test_galaxy_spec test_galaxy_yaml
172 172
 
173 173
 test_galaxy_spec:
174 174
 	mytmpdir=$(MYTMPDIR) ; \
175
-	ansible-galaxy install -r galaxy_rolesfile -p $$mytmpdir/roles ; \
175
+	ansible-galaxy install -r galaxy_rolesfile -p $$mytmpdir/roles -vvvv ; \
176 176
     cp galaxy_playbook.yml $$mytmpdir ; \
177 177
     ansible-playbook -i $(INVENTORY) $$mytmpdir/galaxy_playbook.yml -v $(TEST_FLAGS) ; \
178 178
     RC=$$? ; \
... ...
@@ -181,7 +181,7 @@ test_galaxy_spec:
181 181
 
182 182
 test_galaxy_yaml:
183 183
 	mytmpdir=$(MYTMPDIR) ; \
184
-	ansible-galaxy install -r galaxy_roles.yml -p $$mytmpdir/roles ; \
184
+	ansible-galaxy install -r galaxy_roles.yml -p $$mytmpdir/roles -vvvv; \
185 185
     cp galaxy_playbook.yml $$mytmpdir ; \
186 186
     ansible-playbook -i $(INVENTORY) $$mytmpdir/galaxy_playbook.yml -v $(TEST_FLAGS) ; \
187 187
     RC=$$? ; \