From 0ed97e4d111ac39004854121e846deac696261c7 Mon Sep 17 00:00:00 2001
From: James Cammarata <jimi@sngx.net>
Date: Tue, 21 Oct 2014 10:10:03 -0500
Subject: [PATCH] Updating v2 Role class code

---
 v2/ansible/parsing/yaml/objects.py |  9 +++
 v2/ansible/playbook/role.py        | 92 ++++++++++++++++++------------
 2 files changed, 64 insertions(+), 37 deletions(-)

diff --git a/v2/ansible/parsing/yaml/objects.py b/v2/ansible/parsing/yaml/objects.py
index 6fb1631b87b..be687d1e148 100644
--- a/v2/ansible/parsing/yaml/objects.py
+++ b/v2/ansible/parsing/yaml/objects.py
@@ -32,6 +32,15 @@ class AnsibleBaseYAMLObject:
     def get_position_info(self):
         return (self._data_source, self._line_number, self._column_number)
 
+    def copy_position_info(obj):
+        ''' copies the position info from another object '''
+        assert isinstance(obj, AnsibleBaseYAMLObject)
+
+        (src, line, col) = obj.get_position_info()
+        self._data_source   = src
+        self._line_number   = line
+        self._column_number = col
+
 class AnsibleMapping(AnsibleBaseYAMLObject, dict):
     ''' sub class for dictionaries '''
     pass
diff --git a/v2/ansible/playbook/role.py b/v2/ansible/playbook/role.py
index af3855f3d9d..ea4f2af67f6 100644
--- a/v2/ansible/playbook/role.py
+++ b/v2/ansible/playbook/role.py
@@ -27,21 +27,23 @@ from ansible.playbook.attribute import FieldAttribute
 from ansible.playbook.base import Base
 from ansible.playbook.block import Block
 from ansible.parsing import load_data_from_file
+from ansible.errors import AnsibleError
 
-#from ansible.utils import list_union, unfrackpath
+from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
 
 class Role(Base):
 
-    _role           = FieldAttribute(isa='string')
+    _role_name      = FieldAttribute(isa='string')
+    _role_path      = FieldAttribute(isa='string')
     _src            = FieldAttribute(isa='string')
     _scm            = FieldAttribute(isa='string')
     _version        = FieldAttribute(isa='string')
-    _params         = FieldAttribute(isa='dict')
-    _metadata       = FieldAttribute(isa='dict')
-    _task_blocks    = FieldAttribute(isa='list')
-    _handler_blocks = FieldAttribute(isa='list')
-    _default_vars   = FieldAttribute(isa='dict')
-    _role_vars      = FieldAttribute(isa='dict')
+    _params         = FieldAttribute(isa='dict', default=dict())
+    _metadata       = FieldAttribute(isa='dict', default=dict())
+    _task_blocks    = FieldAttribute(isa='list', default=[])
+    _handler_blocks = FieldAttribute(isa='list', default=[])
+    _default_vars   = FieldAttribute(isa='dict', default=dict())
+    _role_vars      = FieldAttribute(isa='dict', default=dict())
 
     def __init__(self, vault_password=None):
         self._role_path = None
@@ -52,7 +54,7 @@ class Role(Base):
         return self.get_name()
 
     def get_name(self):
-        return self._attributes['role']
+        return self._attributes['role_name']
 
     @staticmethod
     def load(data, vault_password=None):
@@ -65,44 +67,52 @@ class Role(Base):
     # munge, and other functions used for loading the ds
 
     def munge(self, ds):
-        # Role definitions can be strings or dicts, so we fix
-        # things up here. Anything that is not a role name, tag,
-        # or conditional will also be added to the params sub-
-        # dictionary for loading later
+        # create the new ds as an AnsibleMapping, so we can preserve any line/column
+        # data from the parser, and copy that info from the old ds (if applicable)
+        new_ds = AnsibleMapping()
+        if isinstance(ds, AnsibleBaseYAMLObject):
+            new_ds.copy_position_info(ds)
+
+        # Role definitions can be strings or dicts, so we fix things up here.
+        # Anything that is not a role name, tag, or conditional will also be
+        # added to the params sub-dictionary for loading later
         if isinstance(ds, string_types):
-            new_ds = dict(role=ds)
+            new_ds['role_name'] = ds
         else:
+            # munge the role ds here to correctly fill in the various fields which
+            # may be used to define the role, like: role, src, scm, etc.
             ds = self._munge_role(ds)
 
+            # now we split any random role params off from the role spec and store
+            # them in a dictionary of params for parsing later
             params = dict()
-            new_ds = dict()
-
+            attr_names = [attr_name for (attr_name, attr_value) in self._get_base_attributes().iteritems()]
             for (key, value) in iteritems(ds):
-                if key not in [name for (name, value) in self._get_base_attributes().iteritems()]:
-                    # this key does not match a field attribute,
-                    # so it must be a role param
+                if key not in attr_names and key != 'role':
+                    # this key does not match a field attribute, so it must be a role param
                     params[key] = value
                 else:
                     # this is a field attribute, so copy it over directly
                     new_ds[key] = value
-
-            # finally, assign the params to a new entry in the revised ds
             new_ds['params'] = params
 
-        # set the role path, based on the role definition
-        self._role_path = self._get_role_path(new_ds.get('role'))
+        # Set the role name and path, based on the role definition
+        (role_name, role_path) = self._get_role_path(new_ds.get('role_name'))
+        new_ds['role_name'] = role_name
+        new_ds['role_path'] = role_path
 
         # load the role's files, if they exist
-        new_ds['metadata']       = self._load_role_yaml('meta')
-        new_ds['task_blocks']    = self._load_role_yaml('tasks')
-        new_ds['handler_blocks'] = self._load_role_yaml('handlers')
-        new_ds['default_vars']   = self._load_role_yaml('defaults')
-        new_ds['role_vars']      = self._load_role_yaml('vars')
+        new_ds['metadata']       = self._load_role_yaml(role_path, 'meta')
+        new_ds['task_blocks']    = self._load_role_yaml(role_path, 'tasks')
+        new_ds['handler_blocks'] = self._load_role_yaml(role_path, 'handlers')
+        new_ds['default_vars']   = self._load_role_yaml(role_path, 'defaults')
+        new_ds['role_vars']      = self._load_role_yaml(role_path, 'vars')
 
+        # and return the newly munged ds
         return new_ds
 
-    def _load_role_yaml(self, subdir):
-        file_path = os.path.join(self._role_path, subdir)
+    def _load_role_yaml(self, role_path, subdir):
+        file_path = os.path.join(role_path, subdir)
         if os.path.exists(file_path) and os.path.isdir(file_path):
             main_file = self._resolve_main(file_path)
             if os.path.exists(main_file):
@@ -119,7 +129,7 @@ class Role(Base):
         )
 
         if sum([os.path.isfile(x) for x in possible_mains]) > 1:
-            raise errors.AnsibleError("found multiple main files at %s, only one allowed" % (basepath))
+            raise AnsibleError("found multiple main files at %s, only one allowed" % (basepath))
         else:
             for m in possible_mains:
                 if os.path.isfile(m):
@@ -136,15 +146,23 @@ class Role(Base):
 
         # FIXME: this should use unfrackpath once the utils code has been sorted out
         role_path = os.path.normpath(role)
+        print("first role path is %s" % role_path)
         if os.path.exists(role_path):
-            return role_path
+            role_name = os.path.basename(role)
+            print('returning role path %s' % role_path)
+            return (role_name, role_path)
         else:
             for path in ('./roles', '/etc/ansible/roles'):
                 role_path = os.path.join(path, role)
+		print("current role path is %s" % role_path)
                 if os.path.exists(role_path):
-                    return role_path
-        # FIXME: raise something here
-        raise
+                    print('returning role path %s' % role_path)
+                    return (role, role_path)
+
+        # FIXME: make the parser smart about list/string entries
+        #        in the yaml so the error line/file can be reported
+        #        here 
+        raise AnsibleError("the role '%s' was not found" % role, obj=role)
 
     def _repo_url_to_role_name(self, repo_url):
         # gets the role name out of a repo like
@@ -198,12 +216,12 @@ class Role(Base):
         if len(tokens) == 3:
             role_name = tokens[2]
         else:
-            role_name = repo_url_to_role_name(tokens[0])
+            role_name = self._repo_url_to_role_name(tokens[0])
 
         if scm and not role_version:
             role_version = default_role_versions.get(scm, '')
 
-        return dict(scm=scm, src=role_url, version=role_version, name=role_name)
+        return dict(scm=scm, src=role_url, version=role_version, role_name=role_name)
 
     def _munge_role(self, ds):
         if 'role' in ds: