From e2b60475147204ff5c06de3b4f0e2106ded064ff Mon Sep 17 00:00:00 2001
From: Matt Clay <matt@mystile.com>
Date: Thu, 4 Oct 2018 07:02:42 -0700
Subject: [PATCH] Add symlinks sanity test. (#46467)

* Add symlinks sanity test.
* Replace legacy test symlinks with actual content.
* Remove dir symlink from template_jinja2_latest.
* Update import test to use generated library dir.
* Fix copy test symlink setup.
---
 .../rst/dev_guide/testing/sanity/symlinks.rst |   6 +
 .../subdir/subdir1/ansible-test-abs-link      |   1 -
 .../subdir/subdir1/ansible-test-abs-link-dir  |   1 -
 .../targets/copy/files/subdir/subdir1/invalid |   1 -
 .../copy/files/subdir/subdir1/invalid2        |   1 -
 .../files/subdir/subdir1/out_of_tree_circle   |   1 -
 .../targets/copy/files/subdir/subdir1/subdir3 |   1 -
 test/integration/targets/copy/tasks/main.yml  |  19 ++-
 .../template_jinja2_latest/roles/template     |   1 -
 .../targets/template_jinja2_latest/runme.sh   |   3 +
 test/legacy/roles/setup_ec2                   |   1 -
 test/legacy/roles/setup_ec2/defaults/main.yml |   2 +
 test/legacy/roles/setup_ec2/tasks/common.yml  | 119 ++++++++++++++++++
 test/legacy/roles/setup_ec2/vars/main.yml     |   3 +
 test/legacy/roles/setup_sshkey                |   1 -
 test/legacy/roles/setup_sshkey/tasks/main.yml |  55 ++++++++
 test/runner/lib/sanity/import.py              |  18 ++-
 test/sanity/code-smell/symlinks.json          |   4 +
 test/sanity/code-smell/symlinks.py            |  35 ++++++
 test/sanity/import/lib/ansible/__init__.py    |   1 -
 test/sanity/import/lib/ansible/module_utils   |   1 -
 21 files changed, 259 insertions(+), 16 deletions(-)
 create mode 100644 docs/docsite/rst/dev_guide/testing/sanity/symlinks.rst
 delete mode 120000 test/integration/targets/copy/files/subdir/subdir1/ansible-test-abs-link
 delete mode 120000 test/integration/targets/copy/files/subdir/subdir1/ansible-test-abs-link-dir
 delete mode 120000 test/integration/targets/copy/files/subdir/subdir1/invalid
 delete mode 120000 test/integration/targets/copy/files/subdir/subdir1/invalid2
 delete mode 120000 test/integration/targets/copy/files/subdir/subdir1/out_of_tree_circle
 delete mode 120000 test/integration/targets/copy/files/subdir/subdir1/subdir3
 delete mode 120000 test/integration/targets/template_jinja2_latest/roles/template
 delete mode 120000 test/legacy/roles/setup_ec2
 create mode 100644 test/legacy/roles/setup_ec2/defaults/main.yml
 create mode 100644 test/legacy/roles/setup_ec2/tasks/common.yml
 create mode 100644 test/legacy/roles/setup_ec2/vars/main.yml
 delete mode 120000 test/legacy/roles/setup_sshkey
 create mode 100644 test/legacy/roles/setup_sshkey/tasks/main.yml
 create mode 100644 test/sanity/code-smell/symlinks.json
 create mode 100755 test/sanity/code-smell/symlinks.py
 delete mode 100644 test/sanity/import/lib/ansible/__init__.py
 delete mode 120000 test/sanity/import/lib/ansible/module_utils

diff --git a/docs/docsite/rst/dev_guide/testing/sanity/symlinks.rst b/docs/docsite/rst/dev_guide/testing/sanity/symlinks.rst
new file mode 100644
index 00000000000..66dbf500683
--- /dev/null
+++ b/docs/docsite/rst/dev_guide/testing/sanity/symlinks.rst
@@ -0,0 +1,6 @@
+Sanity Tests ยป symlinks
+=======================
+
+Symbolic links are only permitted for files that exist to ensure proper tarball generation during a release.
+
+If other types of symlinks are needed for tests they must be created as part of the test.
diff --git a/test/integration/targets/copy/files/subdir/subdir1/ansible-test-abs-link b/test/integration/targets/copy/files/subdir/subdir1/ansible-test-abs-link
deleted file mode 120000
index 94491ad891f..00000000000
--- a/test/integration/targets/copy/files/subdir/subdir1/ansible-test-abs-link
+++ /dev/null
@@ -1 +0,0 @@
-/tmp/ansible-test-abs-link
\ No newline at end of file
diff --git a/test/integration/targets/copy/files/subdir/subdir1/ansible-test-abs-link-dir b/test/integration/targets/copy/files/subdir/subdir1/ansible-test-abs-link-dir
deleted file mode 120000
index f5eccbbf289..00000000000
--- a/test/integration/targets/copy/files/subdir/subdir1/ansible-test-abs-link-dir
+++ /dev/null
@@ -1 +0,0 @@
-/tmp/ansible-test-abs-link-dir
\ No newline at end of file
diff --git a/test/integration/targets/copy/files/subdir/subdir1/invalid b/test/integration/targets/copy/files/subdir/subdir1/invalid
deleted file mode 120000
index e466dcbd8e8..00000000000
--- a/test/integration/targets/copy/files/subdir/subdir1/invalid
+++ /dev/null
@@ -1 +0,0 @@
-invalid
\ No newline at end of file
diff --git a/test/integration/targets/copy/files/subdir/subdir1/invalid2 b/test/integration/targets/copy/files/subdir/subdir1/invalid2
deleted file mode 120000
index e1b2509c075..00000000000
--- a/test/integration/targets/copy/files/subdir/subdir1/invalid2
+++ /dev/null
@@ -1 +0,0 @@
-../invalid
\ No newline at end of file
diff --git a/test/integration/targets/copy/files/subdir/subdir1/out_of_tree_circle b/test/integration/targets/copy/files/subdir/subdir1/out_of_tree_circle
deleted file mode 120000
index 218b55eabba..00000000000
--- a/test/integration/targets/copy/files/subdir/subdir1/out_of_tree_circle
+++ /dev/null
@@ -1 +0,0 @@
-/tmp/ansible-test-link-dir/out_of_tree_circle
\ No newline at end of file
diff --git a/test/integration/targets/copy/files/subdir/subdir1/subdir3 b/test/integration/targets/copy/files/subdir/subdir1/subdir3
deleted file mode 120000
index 15b47586b55..00000000000
--- a/test/integration/targets/copy/files/subdir/subdir1/subdir3
+++ /dev/null
@@ -1 +0,0 @@
-../subdir2/subdir3
\ No newline at end of file
diff --git a/test/integration/targets/copy/tasks/main.yml b/test/integration/targets/copy/tasks/main.yml
index c5f6c03bb43..e1ae5a46b6d 100644
--- a/test/integration/targets/copy/tasks/main.yml
+++ b/test/integration/targets/copy/tasks/main.yml
@@ -9,15 +9,25 @@
         local_temp_dir: '{{ tempfile_result.stdout }}'
         # output_dir is hardcoded in test/runner/lib/executor.py and created there
         remote_dir: '{{ output_dir }}'
+        symlinks:
+          ansible-test-abs-link: /tmp/ansible-test-abs-link
+          ansible-test-abs-link-dir: /tmp/ansible-test-abs-link-dir
+          circles: ../
+          invalid: invalid
+          invalid2: ../invalid
+          out_of_tree_circle: /tmp/ansible-test-link-dir/out_of_tree_circle
+          subdir3: ../subdir2/subdir3
 
     - file: path={{local_temp_dir}} state=directory
       name: ensure temp dir exists
 
     # file cannot do this properly, use command instead
-    - name: Create circular symbolic link
-      command: ln -s ../ circles
+    - name: Create symbolic link
+      command: "ln -s '{{ item.value }}' '{{ item.key }}'"
       args:
         chdir: '{{role_path}}/files/subdir/subdir1'
+        warn: no
+      with_dict: "{{ symlinks }}"
 
     - name: Create remote unprivileged remote user
       user:
@@ -55,11 +65,12 @@
         state: absent
       connection: local
 
-    - name: Remove circular symbolic link
+    - name: Remove symbolic link
       file:
-        path: '{{ role_path }}/files/subdir/subdir1/circles'
+        path: '{{ role_path }}/files/subdir/subdir1/{{ item.key }}'
         state: absent
       connection: local
+      with_dict: "{{ symlinks }}"
 
     - name: Remote unprivileged remote user
       user:
diff --git a/test/integration/targets/template_jinja2_latest/roles/template b/test/integration/targets/template_jinja2_latest/roles/template
deleted file mode 120000
index 8bed5e1595c..00000000000
--- a/test/integration/targets/template_jinja2_latest/roles/template
+++ /dev/null
@@ -1 +0,0 @@
-../../template
\ No newline at end of file
diff --git a/test/integration/targets/template_jinja2_latest/runme.sh b/test/integration/targets/template_jinja2_latest/runme.sh
index 3c705e19a06..9f61e4879c1 100755
--- a/test/integration/targets/template_jinja2_latest/runme.sh
+++ b/test/integration/targets/template_jinja2_latest/runme.sh
@@ -18,4 +18,7 @@ source "${MYTMPDIR}/jinja2/bin/activate"
 
 pip install -U jinja2
 
+ANSIBLE_ROLES_PATH="$(dirname "$(pwd)")"
+export ANSIBLE_ROLES_PATH
+
 ansible-playbook -i ../../inventory main.yml -e @../../integration_config.yml -v "$@"
diff --git a/test/legacy/roles/setup_ec2 b/test/legacy/roles/setup_ec2
deleted file mode 120000
index 6dd2acb9e82..00000000000
--- a/test/legacy/roles/setup_ec2
+++ /dev/null
@@ -1 +0,0 @@
-../../integration/targets/setup_ec2
\ No newline at end of file
diff --git a/test/legacy/roles/setup_ec2/defaults/main.yml b/test/legacy/roles/setup_ec2/defaults/main.yml
new file mode 100644
index 00000000000..fb1f88b1ecb
--- /dev/null
+++ b/test/legacy/roles/setup_ec2/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+resource_prefix: 'ansible-testing-'
diff --git a/test/legacy/roles/setup_ec2/tasks/common.yml b/test/legacy/roles/setup_ec2/tasks/common.yml
new file mode 100644
index 00000000000..bf23f539a9e
--- /dev/null
+++ b/test/legacy/roles/setup_ec2/tasks/common.yml
@@ -0,0 +1,119 @@
+---
+
+# ============================================================
+- name: test with no parameters
+  action: "{{module_name}}"
+  register: result
+  ignore_errors: true
+
+- name: assert failure when called with no parameters
+  assert:
+    that:
+       - 'result.failed'
+       - 'result.msg == "missing required arguments: name"'
+
+# ============================================================
+- name: test with only name
+  action: "{{module_name}} name={{ec2_key_name}}"
+  register: result
+  ignore_errors: true
+
+- name: assert failure when called with only 'name'
+  assert:
+    that:
+       - 'result.failed'
+       - 'result.msg == "Either region or ec2_url must be specified"'
+
+# ============================================================
+- name: test invalid region parameter
+  action: "{{module_name}} name='{{ec2_key_name}}' region='asdf querty 1234'"
+  register: result
+  ignore_errors: true
+
+- name: assert invalid region parameter
+  assert:
+    that:
+       - 'result.failed'
+       - 'result.msg.startswith("value of region must be one of:")'
+
+# ============================================================
+- name: test valid region parameter
+  action: "{{module_name}} name='{{ec2_key_name}}' region='{{ec2_region}}'"
+  register: result
+  ignore_errors: true
+
+- name: assert valid region parameter
+  assert:
+    that:
+       - 'result.failed'
+       - 'result.msg.startswith("No handler was ready to authenticate.")'
+
+# ============================================================
+- name: test environment variable EC2_REGION
+  action: "{{module_name}} name='{{ec2_key_name}}'"
+  environment:
+    EC2_REGION: '{{ec2_region}}'
+  register: result
+  ignore_errors: true
+
+- name: assert environment variable EC2_REGION
+  assert:
+    that:
+       - 'result.failed'
+       - 'result.msg.startswith("No handler was ready to authenticate.")'
+
+# ============================================================
+- name: test invalid ec2_url parameter
+  action: "{{module_name}} name='{{ec2_key_name}}'"
+  environment:
+    EC2_URL: bogus.example.com
+  register: result
+  ignore_errors: true
+
+- name: assert invalid ec2_url parameter
+  assert:
+    that:
+       - 'result.failed'
+       - 'result.msg.startswith("No handler was ready to authenticate.")'
+
+# ============================================================
+- name: test valid ec2_url parameter
+  action: "{{module_name}} name='{{ec2_key_name}}'"
+  environment:
+    EC2_URL: '{{ec2_url}}'
+  register: result
+  ignore_errors: true
+
+- name: assert valid ec2_url parameter
+  assert:
+    that:
+       - 'result.failed'
+       - 'result.msg.startswith("No handler was ready to authenticate.")'
+
+# ============================================================
+- name: test credentials from environment
+  action: "{{module_name}} name='{{ec2_key_name}}'"
+  environment:
+    EC2_REGION: '{{ec2_region}}'
+    EC2_ACCESS_KEY: bogus_access_key
+    EC2_SECRET_KEY: bogus_secret_key
+  register: result
+  ignore_errors: true
+
+- name: assert ec2_key with valid ec2_url
+  assert:
+    that:
+       - 'result.failed'
+       - '"EC2ResponseError: 401 Unauthorized" in result.msg'
+
+# ============================================================
+- name: test credential parameters
+  action: "{{module_name}} name='{{ec2_key_name}}' ec2_region='{{ec2_region}}' ec2_access_key=bogus_access_key ec2_secret_key=bogus_secret_key"
+  register: result
+  ignore_errors: true
+
+- name: assert credential parameters
+  assert:
+    that:
+       - 'result.failed'
+       - '"EC2ResponseError: 401 Unauthorized" in result.msg'
diff --git a/test/legacy/roles/setup_ec2/vars/main.yml b/test/legacy/roles/setup_ec2/vars/main.yml
new file mode 100644
index 00000000000..3d7209ef1b0
--- /dev/null
+++ b/test/legacy/roles/setup_ec2/vars/main.yml
@@ -0,0 +1,3 @@
+---
+ec2_url: ec2.amazonaws.com
+ec2_region: us-east-1
diff --git a/test/legacy/roles/setup_sshkey b/test/legacy/roles/setup_sshkey
deleted file mode 120000
index 7f1d3b39fa3..00000000000
--- a/test/legacy/roles/setup_sshkey
+++ /dev/null
@@ -1 +0,0 @@
-../../integration/targets/setup_sshkey
\ No newline at end of file
diff --git a/test/legacy/roles/setup_sshkey/tasks/main.yml b/test/legacy/roles/setup_sshkey/tasks/main.yml
new file mode 100644
index 00000000000..18c571b6718
--- /dev/null
+++ b/test/legacy/roles/setup_sshkey/tasks/main.yml
@@ -0,0 +1,55 @@
+# (c) 2014, James Laska <jlaska@ansible.com>
+
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
+
+- name: create a temp file
+  tempfile:
+    state: file
+  register: sshkey_file
+  tags:
+    - prepare
+
+- name: generate sshkey
+  shell: echo 'y' | ssh-keygen -P '' -f {{ sshkey_file.path }}
+  tags:
+    - prepare
+
+- name: create another temp file
+  tempfile:
+    state: file
+  register: another_sshkey_file
+  tags:
+    - prepare
+
+- name: generate another_sshkey
+  shell: echo 'y' | ssh-keygen -P '' -f {{ another_sshkey_file.path }}
+  tags:
+    - prepare
+
+- name: record fingerprint
+  shell: openssl rsa -in {{ sshkey_file.path }} -pubout -outform DER 2>/dev/null | openssl md5 -c
+  register: fingerprint
+  tags:
+    - prepare
+
+- name: set facts for future roles
+  set_fact:
+    sshkey: '{{ sshkey_file.path }}'
+    key_material: "{{ lookup('file', sshkey_file.path ~ '.pub') }}"
+    another_key_material: "{{ lookup('file', another_sshkey_file.path ~ '.pub') }}"
+    fingerprint: '{{ fingerprint.stdout.split()[1] }}'
+  tags:
+    - prepare
diff --git a/test/runner/lib/sanity/import.py b/test/runner/lib/sanity/import.py
index 5f63ac21107..7ff5212eb97 100644
--- a/test/runner/lib/sanity/import.py
+++ b/test/runner/lib/sanity/import.py
@@ -20,6 +20,7 @@ from lib.util import (
     find_python,
     read_lines_without_comments,
     parse_to_list_of_dict,
+    make_dirs,
 )
 
 from lib.ansible_util import (
@@ -81,9 +82,24 @@ class ImportTest(SanityMultipleVersion):
         if not args.explain:
             os.symlink(os.path.abspath('test/sanity/import/importer.py'), importer_path)
 
+        # create a minimal python library
+        python_path = os.path.abspath('test/runner/.tox/import/lib')
+        ansible_path = os.path.join(python_path, 'ansible')
+        ansible_init = os.path.join(ansible_path, '__init__.py')
+        ansible_link = os.path.join(ansible_path, 'module_utils')
+
+        if not args.explain:
+            make_dirs(ansible_path)
+
+            with open(ansible_init, 'w'):
+                pass
+
+            if not os.path.exists(ansible_link):
+                os.symlink('../../../../../../lib/ansible/module_utils', ansible_link)
+
         # activate the virtual environment
         env['PATH'] = '%s:%s' % (virtual_environment_bin, env['PATH'])
-        env['PYTHONPATH'] = os.path.abspath('test/sanity/import/lib')
+        env['PYTHONPATH'] = python_path
 
         # make sure coverage is available in the virtual environment if needed
         if args.coverage:
diff --git a/test/sanity/code-smell/symlinks.json b/test/sanity/code-smell/symlinks.json
new file mode 100644
index 00000000000..39ac4bd57f0
--- /dev/null
+++ b/test/sanity/code-smell/symlinks.json
@@ -0,0 +1,4 @@
+{
+    "always": true,
+    "output": "path-message"
+}
diff --git a/test/sanity/code-smell/symlinks.py b/test/sanity/code-smell/symlinks.py
new file mode 100755
index 00000000000..ec5c72635b7
--- /dev/null
+++ b/test/sanity/code-smell/symlinks.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+
+import os
+
+
+def main():
+    skip_dirs = set([
+        '.tox',
+    ])
+
+    for root, dirs, files in os.walk('.'):
+        for skip_dir in skip_dirs:
+            if skip_dir in dirs:
+                dirs.remove(skip_dir)
+
+        if root == '.':
+            root = ''
+        elif root.startswith('./'):
+            root = root[2:]
+
+        for file in files:
+            path = os.path.join(root, file)
+
+            if not os.path.exists(path):
+                print('%s: broken symlinks are not allowed' % path)
+
+        for directory in dirs:
+            path = os.path.join(root, directory)
+
+            if os.path.islink(path):
+                print('%s: symlinks to directories are not allowed' % path)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/test/sanity/import/lib/ansible/__init__.py b/test/sanity/import/lib/ansible/__init__.py
deleted file mode 100644
index efba395dd1e..00000000000
--- a/test/sanity/import/lib/ansible/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Empty placeholder for import sanity test."""
diff --git a/test/sanity/import/lib/ansible/module_utils b/test/sanity/import/lib/ansible/module_utils
deleted file mode 120000
index fd236e25b54..00000000000
--- a/test/sanity/import/lib/ansible/module_utils
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../lib/ansible/module_utils
\ No newline at end of file