Repository: ansible/ansible-lint

Issue:
Ansible 2: Inconsistent parsing of blocks ?
Reference: http://docs.ansible.com/ansible/playbooks_blocks.html

It looks like ansible-lint is not able to parse blocks properly, here's how to reproduce with the sample block taken from the documentation.

Setup:

```
  $ virtualenv lint
  New python executable in lint/bin/python
  Installing setuptools, pip, wheel...done.

  $ . ./lint/bin/activate.fish

  (lint) $ pip install 'ansible>=2.0' 'ansible-lint>=2.3'
  Collecting ansible>=2.0
  Collecting ansible-lint>=2.3
    Downloading ansible-lint-2.3.0.tar.gz
  Collecting paramiko (from ansible>=2.0)
    Using cached paramiko-1.16.0-py2.py3-none-any.whl
  Requirement already satisfied (use --upgrade to upgrade): setuptools in ./lint/lib/python2.7/site-packages (from ansible>=2.0)
  Collecting PyYAML (from ansible>=2.0)
  Collecting pycrypto>=2.6 (from ansible>=2.0)
  Collecting jinja2 (from ansible>=2.0)
    Using cached Jinja2-2.8-py2.py3-none-any.whl
  Collecting ecdsa>=0.11 (from paramiko->ansible>=2.0)
    Using cached ecdsa-0.13-py2.py3-none-any.whl
  Collecting MarkupSafe (from jinja2->ansible>=2.0)
  Building wheels for collected packages: ansible-lint
    Running setup.py bdist_wheel for ansible-lint
    Stored in directory: /home/dmsimard/.cache/pip/wheels/67/91/11/eddbc9181f0c591aef25dd203bba33e94ad43c78c715d04852
  Successfully built ansible-lint
  Installing collected packages: ecdsa, pycrypto, paramiko, PyYAML, MarkupSafe, jinja2, ansible, ansible-lint
  Successfully installed MarkupSafe-0.23 PyYAML-3.11 ansible-2.0.0.1 ansible-lint-2.3.0 ecdsa-0.13 jinja2-2.8 paramiko-1.16.0 pycrypto-2.6.1

  (lint) $ mkdir -p playbooks/roles/test/tasks
```

**playbooks/playbook.yml**

```

---
- name: Include test role
  hosts: all
  gather_facts: no
  roles:
    - role: test
```

**playbooks/roles/tasks/main.yml**

```

---
- block:
    - debug: msg='i execute normally'
    - command: /bin/false
    - debug: msg='i never execute, cause ERROR!'
  rescue:
    - debug: msg='I caught an error'
    - command: /bin/false
    - debug: msg='I also never execute :-('
  always:
    - debug: msg="this always executes"
```

**ansible-lint output**

```
  (lint) $ ansible-lint playbooks/playbook.yml; echo $status
  Was not expecting value [{'debug': 'msg="this always executes"', '__line__': 11}] of type <type 'list'> for key always
  Task: {'always': [{'debug': 'msg="this always executes"', '__line__': 11}], 'block': [{'debug': "msg='i execute normally'", '__line__': 3}, {'command': '/bin/false', '__line__': 4}, {'debug': "msg='i never execute, cause ERROR!'", '__line__': 5}], 'rescue': [{'debug': "msg='I caught an error'", '__line__': 7}, {'command': '/bin/false', '__line__': 8}, {'debug': "msg='I also never execute :-('", '__line__': 9}], '__line__': 2}
  1
  (lint) $ ansible-lint playbooks/roles/test/tasks/main.yml; echo $status
  0
```



Discussion:

Resolving patch:
diff --git a/lib/ansiblelint/utils.py b/lib/ansiblelint/utils.py
--- a/lib/ansiblelint/utils.py
+++ b/lib/ansiblelint/utils.py
@@ -266,17 +266,26 @@ def task_to_str(task):
                     action.get("module_arguments"))
     return "{0} {1}".format(action["module"], args)
 
+def extract_from_list(blocks, candidates):
+    results = list()
+    for block in blocks:
+        for candidate in candidates:
+            if candidate in block:
+                results.extend(block[candidate])
+    return results
 
 def get_action_tasks(yaml, file):
     tasks = list()
     if file['type'] in ['tasks', 'handlers']:
         tasks = yaml
     else:
-        for block in yaml:
-            for section in ['tasks', 'handlers', 'pre_tasks', 'post_tasks']:
-                if section in block:
-                    block_tasks = block.get(section) or []
-                    tasks.extend(block_tasks)
+        tasks.extend(extract_from_list(yaml, ['tasks', 'handlers', 'pre_tasks', 'post_tasks']))
+
+    # Add sub-elements of block/rescue/always to tasks list
+    tasks.extend(extract_from_list(tasks, ['block', 'rescue', 'always']))
+    # Remove block/rescue/always elements from tasks list
+    tasks[:] = [task for task in tasks if all (k not in task for k in ('block', 'rescue', 'always'))]
+
     return [normalize_task(task) for task in tasks
             if 'include' not in task.keys()]
 


Test patch:
diff --git a/test/TestRunner.py b/test/TestRunner.py
--- a/test/TestRunner.py
+++ b/test/TestRunner.py
@@ -46,3 +46,8 @@ def test_runner_excludes_paths(self):
         excludes = ['examples/lots_of_warnings.yml']
         runner = ansiblelint.Runner(self.rules, files, [], [], excludes)
         assert (len(runner.run()) == 0)
+
+    def test_runner_block_count(self):
+        files = {'test/block.yml'}
+        runner = ansiblelint.Runner(self.rules, files, [], [], [])
+        assert (len(runner.run()) == 0)
diff --git a/test/block.yml b/test/block.yml
new file mode 100644
--- /dev/null
+++ b/test/block.yml
@@ -0,0 +1,14 @@
+---
+- hosts: all
+
+  tasks:
+  - block:
+      - debug: msg='i execute normally'
+      - command: /bin/false
+      - debug: msg='i never execute, cause ERROR!'
+    rescue:
+      - debug: msg='I caught an error'
+      - command: /bin/false
+      - debug: msg='I also never execute :-('
+    always:
+      - debug: msg="this always executes"