Browse code

Fix ansible-test default image. (#31966)

* Add openssh-client to default docker container.
* Include Azure requirements in default container.

To do so, handling of pip requirements was updated to install each
set of requirements separately and then run a verification pass to
make sure there are no conflicts between requirements.

* Add missing --docker-no-pull option.
* Add documentation for the azure-requirements test.

(cherry picked from commit 36b13e3e3d2d7d976adb3f976527fc916285777c)

Matt Clay authored on 2017/10/21 00:48:01
Showing 10 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+Sanity Tests ยป azure-requirements
1
+=================================
2
+
3
+Update the Azure integration test requirements file when changes are made to the Azure packaging requirements file:
4
+
5
+.. code-block:: bash
6
+
7
+    cp packaging/requirements/requirements-azure.txt test/runner/requirements/integration.cloud.azure.txt
8
+
9
+This copy of the requirements file is used when building the ``ansible/ansible:default`` Docker container from ``test/runner/Dockerfile``.
0 10
new file mode 100644
1 11
deleted file mode 100644
... ...
@@ -1,8 +0,0 @@
1
-- name: install cryptography requirement
2
-  # preempt the installation of cryptography from requirements-azure.txt to limit the version installed
3
-  # requests[security] requires cryptography >= 1.3.4
4
-  # cryptography 2.1 requires pip 8.1.2+
5
-  command: pip install cryptography>=1.3.4,<2.1
6
-
7
-- pip:
8
-    requirements: '{{ role_path }}/../../../../packaging/requirements/requirements-azure.txt'
... ...
@@ -13,6 +13,7 @@ RUN apt-get update -y && \
13 13
     libxslt1-dev \
14 14
     locales \
15 15
     make \
16
+    openssh-client \
16 17
     python2.6-dev \
17 18
     python2.7-dev \
18 19
     python3.5-dev \
... ...
@@ -11,13 +11,60 @@ requirements=()
11 11
 
12 12
 for requirement in *.txt; do
13 13
     if [ "${requirement}" != "constraints.txt" ]; then
14
-        requirements+=("-r" "${requirement}")
14
+        requirements+=("${requirement}")
15 15
     fi
16 16
 done
17 17
 
18 18
 for python_version in "${python_versions[@]}"; do
19
+    version_requirements=()
20
+
21
+    for requirement in "${requirements[@]}"; do
22
+        case "${python_version}" in
23
+            "2.6")
24
+                case "${requirement}" in
25
+                    "integration.cloud.azure.txt") continue ;;
26
+                esac
27
+        esac
28
+
29
+        version_requirements+=("${requirement}")
30
+    done
31
+
32
+    echo "==> Installing pip for python ${python_version} ..."
33
+
19 34
     set -x
20 35
     "python${python_version}" /tmp/get-pip.py -c constraints.txt
21
-    "pip${python_version}" install --disable-pip-version-check -c constraints.txt "${requirements[@]}"
22 36
     set +x
37
+
38
+    echo "==> Installing requirements for python ${python_version} ..."
39
+
40
+    for requirement in "${version_requirements[@]}"; do
41
+        set -x
42
+        "pip${python_version}" install --disable-pip-version-check -c constraints.txt -r "${requirement}"
43
+        set +x
44
+    done
45
+
46
+    echo "==> Checking for requirements conflicts for ${python_version} ..."
47
+
48
+    after=$("pip${python_version}" list)
49
+
50
+    for requirement in "${version_requirements[@]}"; do
51
+        before="${after}"
52
+
53
+        set -x
54
+        "pip${python_version}" install --disable-pip-version-check -c constraints.txt -r "${requirement}"
55
+        set +x
56
+
57
+        after=$("pip${python_version}" list)
58
+
59
+        if [ "${before}" != "${after}" ]; then
60
+            echo "==> Conflicts detected in requirements for python ${python_version}: ${requirement}"
61
+            echo ">>> Before"
62
+            echo "${before}"
63
+            echo ">>> After"
64
+            echo "${after}"
65
+            exit 1
66
+        fi
67
+    done
68
+
69
+    echo "==> Finished with requirements for python ${python_version}."
23 70
 done
... ...
@@ -13,6 +13,7 @@ import textwrap
13 13
 import functools
14 14
 import shutil
15 15
 import stat
16
+import pipes
16 17
 import random
17 18
 import string
18 19
 import atexit
... ...
@@ -162,30 +163,78 @@ def install_command_requirements(args):
162 162
         if args.junit:
163 163
             packages.append('junit-xml')
164 164
 
165
-    extras = []
165
+    commands = [generate_pip_install(args.command, packages=packages)]
166 166
 
167 167
     if isinstance(args, IntegrationConfig):
168
-        extras += ['cloud.%s' % cp for cp in get_cloud_platforms(args)]
168
+        for cloud_platform in get_cloud_platforms(args):
169
+            commands.append(generate_pip_install('%s.cloud.%s' % (args.command, cloud_platform)))
169 170
 
170
-    cmd = generate_pip_install(args.command, packages, extras)
171
+    # only look for changes when more than one requirements file is needed
172
+    detect_pip_changes = len(commands) > 1
171 173
 
172
-    if not cmd:
173
-        return
174
+    # first pass to install requirements, changes expected unless environment is already set up
175
+    changes = run_pip_commands(args, commands, detect_pip_changes)
174 176
 
175
-    try:
176
-        run_command(args, cmd)
177
-    except SubprocessError as ex:
178
-        if ex.status != 2:
179
-            raise
177
+    if not changes:
178
+        return  # no changes means we can stop early
179
+
180
+    # second pass to check for conflicts in requirements, changes are not expected here
181
+    changes = run_pip_commands(args, commands, detect_pip_changes)
182
+
183
+    if not changes:
184
+        return  # no changes means no conflicts
185
+
186
+    raise ApplicationError('Conflicts detected in requirements. The following commands reported changes during verification:\n%s' %
187
+                           '\n'.join((' '.join(pipes.quote(c) for c in cmd) for cmd in changes)))
188
+
189
+
190
+def run_pip_commands(args, commands, detect_pip_changes=False):
191
+    """
192
+    :type args: EnvironmentConfig
193
+    :type commands: list[list[str]]
194
+    :type detect_pip_changes: bool
195
+    :rtype: list[list[str]]
196
+    """
197
+    changes = []
198
+
199
+    after_list = pip_list(args) if detect_pip_changes else None
200
+
201
+    for cmd in commands:
202
+        if not cmd:
203
+            continue
180 204
 
181
-        # If pip is too old it won't understand the arguments we passed in, so we'll need to upgrade it.
205
+        before_list = after_list
182 206
 
183
-        # Installing "coverage" on ubuntu 16.04 fails with the error:
184
-        # AttributeError: 'Requirement' object has no attribute 'project_name'
185
-        # See: https://bugs.launchpad.net/ubuntu/xenial/+source/python-pip/+bug/1626258
186
-        # Upgrading pip works around the issue.
187
-        run_command(args, ['pip', 'install', '--upgrade', 'pip'])
188
-        run_command(args, cmd)
207
+        try:
208
+            run_command(args, cmd)
209
+        except SubprocessError as ex:
210
+            if ex.status != 2:
211
+                raise
212
+
213
+            # If pip is too old it won't understand the arguments we passed in, so we'll need to upgrade it.
214
+
215
+            # Installing "coverage" on ubuntu 16.04 fails with the error:
216
+            # AttributeError: 'Requirement' object has no attribute 'project_name'
217
+            # See: https://bugs.launchpad.net/ubuntu/xenial/+source/python-pip/+bug/1626258
218
+            # Upgrading pip works around the issue.
219
+            run_command(args, ['pip', 'install', '--upgrade', 'pip'])
220
+            run_command(args, cmd)
221
+
222
+        after_list = pip_list(args) if detect_pip_changes else None
223
+
224
+        if before_list != after_list:
225
+            changes.append(cmd)
226
+
227
+    return changes
228
+
229
+
230
+def pip_list(args):
231
+    """
232
+    :type args: EnvironmentConfig
233
+    :rtype: str
234
+    """
235
+    stdout, _ = run_command(args, ['pip', 'list'], capture=True, always=True)
236
+    return stdout
189 237
 
190 238
 
191 239
 def generate_egg_info(args):
192 240
new file mode 100644
... ...
@@ -0,0 +1,16 @@
0
+packaging
1
+requests[security]
2
+azure-mgmt-compute>=2.0.0,<3
3
+azure-mgmt-network>=1.3.0,<2
4
+azure-mgmt-storage>=1.2.0,<2
5
+azure-mgmt-resource>=1.1.0,<2
6
+azure-storage>=0.35.1,<0.36
7
+azure-cli-core>=2.0.12,<3
8
+msrest!=0.4.15
9
+msrestazure>=0.4.11,<0.5
10
+azure-mgmt-dns>=1.0.1,<2
11
+azure-mgmt-keyvault>=0.40.0,<0.41
12
+azure-mgmt-batch>=4.1.0,<5
13
+azure-mgmt-sql>=0.7.1,<0.8
14
+azure-mgmt-web>=0.32.0,<0.33
15
+azure-mgmt-containerservice>=1.0.0
0 16
\ No newline at end of file
... ...
@@ -255,6 +255,8 @@ def parse_args():
255 255
                                      targets=walk_network_integration_targets,
256 256
                                      config=NetworkIntegrationConfig)
257 257
 
258
+    add_extra_docker_options(network_integration, integration=False)
259
+
258 260
     network_integration.add_argument('--platform',
259 261
                                      metavar='PLATFORM',
260 262
                                      action='append',
... ...
@@ -272,6 +274,8 @@ def parse_args():
272 272
                                      targets=walk_windows_integration_targets,
273 273
                                      config=WindowsIntegrationConfig)
274 274
 
275
+    add_extra_docker_options(windows_integration, integration=False)
276
+
275 277
     windows_integration.add_argument('--windows',
276 278
                                      metavar='VERSION',
277 279
                                      action='append',
278 280
new file mode 100755
... ...
@@ -0,0 +1,12 @@
0
+#!/usr/bin/env python
1
+"""Make sure the Azure requirements files match."""
2
+
3
+import filecmp
4
+
5
+src = 'packaging/requirements/requirements-azure.txt'
6
+dst = 'test/runner/requirements/integration.cloud.azure.txt'
7
+
8
+if not filecmp.cmp(src, dst):
9
+    print('Update the Azure integration test requirements with the packaging test requirements:')
10
+    print('cp %s %s' % (src, dst))
11
+    exit(1)
... ...
@@ -5,6 +5,7 @@ constraints=$(
5 5
         | grep -v '(sanity_ok)$' \
6 6
         | sed 's/ *;.*$//; s/ #.*$//' \
7 7
         | grep -v '/constraints.txt:' \
8
+        | grep -v '/integration.cloud.azure.txt:' \
8 9
         | grep '[<>=]'
9 10
 )
10 11