Browse Source

Merge "package-installs: provide for skip from env var"

tags/2.19.0
Zuul 5 months ago
parent
commit
18c0c42c8d

+ 1
- 1
.testr.conf View File

@@ -3,7 +3,7 @@ test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
3 3
              OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
4 4
              OS_LOG_CAPTURE=${OS_LOG_CAPTURE:-1} \
5 5
              OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
6
-             OS_DEBUG=${OS_DEBUG:-0} \
6
+             OS_DEBUG=${OS_DEBUG:-1} \
7 7
              ${PYTHON:-python} -m subunit.run discover . $LISTOPT $IDOPTION
8 8
 test_id_option=--load-list $IDFILE
9 9
 test_list_option=--list

+ 24
- 0
diskimage_builder/elements/package-installs/README.rst View File

@@ -30,6 +30,10 @@ example ``package-installs.yaml``
30 30
     dib_python_version: 2
31 31
   python3-dev:
32 32
     dib_python_version: 3
33
+  package-a:
34
+    when: DIB_USE_PACKAGE_A = 1
35
+  package-b:
36
+    when: DIB_USE_PACKAGE_A != 1
33 37
 
34 38
 example package-installs.json
35 39
 
@@ -62,6 +66,26 @@ architectures the package should be excluded from.  Either ``arch`` or
62 66
 ``not-arch`` can be given for one package - not both.  See
63 67
 documentation about the ARCH variable for more information.
64 68
 
69
+The ``when`` property is a simple ``=`` or ``!=`` match on a value in
70
+an environment variable.  If the given environment variable matches
71
+the operation and value, the package is installed.  If the variable is
72
+not available in the environment, an exception is raised (thus
73
+defaults will likely need to be provided in ``environment.d`` files or
74
+similar for flags used here).  For example, to install an extra
75
+package when a feature is enabled::
76
+
77
+  package:
78
+    when: DIB_FEATURE_FLAG=1
79
+
80
+To install ``package`` when ``DIB_FEATURE_FLAG=0`` but
81
+``other_package`` when ``DIB_FEATURE_FLAG=1`` (i.e. toggle between two
82
+packages), you can use something like::
83
+
84
+  package:
85
+    when: DIB_FEATURE_FLAG=0
86
+  other_package:
87
+    when: DIB_FEATURE_FLAG!=0
88
+
65 89
 DEPRECATED: Adding a file under your elements pre-install.d, install.d, or
66 90
 post-install.d directories called package-installs-<element-name> will cause
67 91
 the list of packages in that file to be installed at the beginning of the

+ 0
- 0
diskimage_builder/elements/package-installs/__init__.py View File


+ 61
- 6
diskimage_builder/elements/package-installs/bin/package-installs-squash View File

@@ -20,6 +20,7 @@ import functools
20 20
 import json
21 21
 import logging
22 22
 import os
23
+import re
23 24
 import sys
24 25
 import yaml
25 26
 
@@ -60,11 +61,54 @@ def _valid_for_arch(pkg_name, arch, not_arch):
60 61
     return not _is_arch_in_list(not_arch)
61 62
 
62 63
 
63
-def collect_data(data, filename, element_name):
64
-    try:
65
-        objs = json.load(open(filename))
66
-    except ValueError:
67
-        objs = yaml.safe_load(open(filename))
64
+def _when(statement):
65
+    '''evaulate a when: statement
66
+
67
+    Evaluate statements of the form
68
+
69
+     when: ENVIRONMENT_VARIABLE[!]=value
70
+
71
+    Returns True if the package should be installed, False otherwise
72
+
73
+    If the ENVIRONMENT_VARIABLE is unset, raises an error
74
+
75
+    '''
76
+    # No statement means install
77
+    if statement is None:
78
+        return True
79
+
80
+    # FOO =  BAR
81
+    # var op val
82
+    match = re.match(
83
+        r"(?P<var>[\w]+)(\s*)(?P<op>=|!=)(\s*)(?P<val>.*)", statement)
84
+    if not match:
85
+        print("Malformed when line: <%s>" % statement)
86
+        sys.exit(1)
87
+    match = match.groupdict()
88
+    var = match['var']
89
+    op = match['op']
90
+    val = match['val']
91
+
92
+    if var not in os.environ:
93
+        raise RuntimeError("The variable <%s> is not set" % var)
94
+
95
+    logger.debug("when eval %s%s%s against <%s>" %
96
+                 (var, op, val, os.environ[var]))
97
+
98
+    if op == '=':
99
+        if val == os.environ[var]:
100
+            return True
101
+    elif op == '!=':
102
+        if val != os.environ[var]:
103
+            return True
104
+    else:
105
+        print("Malformed when op: %s" % op)
106
+        sys.exit(1)
107
+
108
+    return False
109
+
110
+
111
+def collect_data(data, objs, element_name):
68 112
     for pkg_name, params in objs.items():
69 113
         if not params:
70 114
             params = {}
@@ -85,6 +129,12 @@ def collect_data(data, filename, element_name):
85 129
         valid_dib_python_version = (dib_py_version == '' or
86 130
                                     dib_py_version == dib_py_version_env)
87 131
 
132
+        # True means install, false skip
133
+        if _when(params.get('when', None)) is False:
134
+            logger.debug("Skipped due to when: %s/%s" %
135
+                         (element_name, pkg_name))
136
+            continue
137
+
88 138
         if valid_installtype and valid_arch and valid_dib_python_version:
89 139
             data[phase][install].append((pkg_name, element_name))
90 140
 
@@ -126,7 +176,12 @@ def main():
126 176
             if not os.path.exists(target_file):
127 177
                 continue
128 178
             logger.info("Squashing install file: %s" % target_file)
129
-            final_dict = collect_data(final_dict, target_file, element_name)
179
+            try:
180
+                objs = json.load(open(target_file))
181
+            except ValueError:
182
+                objs = yaml.safe_load(open(target_file))
183
+
184
+            final_dict = collect_data(final_dict, objs, element_name)
130 185
 
131 186
     logger.debug("final_dict -> %s" % final_dict)
132 187
 

+ 0
- 0
diskimage_builder/elements/package-installs/tests/__init__.py View File


+ 140
- 0
diskimage_builder/elements/package-installs/tests/test_package_squash.py View File

@@ -0,0 +1,140 @@
1
+# Copyright 2018 Red Hat, Inc.
2
+#
3
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+#    not use this file except in compliance with the License. You may obtain
5
+#    a copy of the License at
6
+#
7
+#         http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+#    Unless required by applicable law or agreed to in writing, software
10
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+#    License for the specific language governing permissions and limitations
13
+#    under the License.
14
+import collections
15
+import functools
16
+import imp
17
+import mock
18
+import os
19
+
20
+from oslotest import base
21
+from testtools.matchers import Mismatch
22
+
23
+installs_squash_src = (os.path.dirname(os.path.realpath(__file__)) +
24
+                       '/../bin/package-installs-squash')
25
+installs_squash = imp.load_source('installs_squash', installs_squash_src)
26
+
27
+
28
+class IsMatchingInstallList(object):
29
+
30
+    def __init__(self, expected):
31
+        self.expected = expected
32
+
33
+    def match(self, actual):
34
+        for phase, ops in self.expected.items():
35
+            if phase not in actual:
36
+                # missing the phase
37
+                return Mismatch(
38
+                    "Phase %d does not exist in %s" % (phase, actual))
39
+            for op, pkgs in ops.items():
40
+                if op not in actual[phase]:
41
+                    # missing op (install/uninstall)
42
+                    return Mismatch(
43
+                        "Operation %s does not exist in %s" % (op, ops))
44
+                # on py2 these can be out of order, we just want a match
45
+                expected_phase_ops = sorted(self.expected[phase][op])
46
+                actual_phase_ops = sorted(actual[phase][op])
47
+                if expected_phase_ops != actual_phase_ops:
48
+                    return Mismatch(
49
+                        "Operation list %s does not match expected  %s" %
50
+                        (actual[phase][op], self.expected[phase][op]))
51
+
52
+
53
+class TestPackageInstall(base.BaseTestCase):
54
+    def setUp(self):
55
+        super(TestPackageInstall, self).setUp()
56
+        self.final_dict = collections.defaultdict(
57
+            functools.partial(collections.defaultdict, list))
58
+
59
+    def test_simple(self):
60
+        '''Test a basic package install'''
61
+        objs = {
62
+            'test_package': ''
63
+        }
64
+
65
+        result = installs_squash.collect_data(
66
+            self.final_dict, objs, 'test_element')
67
+
68
+        expected = {
69
+            'install.d': {
70
+                'install': [('test_package', 'test_element')]
71
+            }
72
+        }
73
+
74
+        self.assertThat(result, IsMatchingInstallList(expected))
75
+
76
+    @mock.patch.object(os, 'environ', dict(ARCH='arm64', **os.environ))
77
+    def test_arch(self):
78
+        '''Exercise the arch and not-arch flags'''
79
+        objs = {
80
+            'test_package': '',
81
+            'test_arm64_package': {
82
+                'arch': 'arm64'
83
+            },
84
+            'do_not_install': {
85
+                'not-arch': 'arm64'
86
+            }
87
+        }
88
+
89
+        result = installs_squash.collect_data(
90
+            self.final_dict, objs, 'test_element')
91
+
92
+        expected = {
93
+            'install.d': {
94
+                'install': [('test_package', 'test_element'),
95
+                            ('test_arm64_package', 'test_element')]
96
+            }
97
+        }
98
+
99
+        self.assertThat(result, IsMatchingInstallList(expected))
100
+
101
+    @mock.patch.object(os, 'environ', dict(DIB_FEATURE='1', **os.environ))
102
+    def test_skip_when(self):
103
+        '''Exercise the when flag'''
104
+        objs = {
105
+            'skipped_package': {
106
+                'when': 'DIB_FEATURE=0'
107
+            },
108
+            'not_skipped_package': {
109
+                'when': 'DIB_FEATURE=1'
110
+            },
111
+            'not_equal_package': {
112
+                'when': 'DIB_FEATURE!=0'
113
+            },
114
+            'not_equal_skipped_package': {
115
+                'when': 'DIB_FEATURE!=1'
116
+            },
117
+        }
118
+
119
+        result = installs_squash.collect_data(
120
+            self.final_dict, objs, 'test_element')
121
+
122
+        expected = {
123
+            'install.d': {
124
+                'install': [('not_skipped_package', 'test_element'),
125
+                            ('not_equal_package', 'test_element')]
126
+            }
127
+        }
128
+
129
+        self.assertThat(result, IsMatchingInstallList(expected))
130
+
131
+    def test_skip_no_var(self):
132
+        '''Exercise the skip_when missing variable failure case'''
133
+        objs = {
134
+            'package': {
135
+                'when': 'MISSING_VAR=1'
136
+            },
137
+        }
138
+
139
+        self.assertRaises(RuntimeError, installs_squash.collect_data,
140
+                          self.final_dict, objs, 'test_element')

+ 7
- 0
releasenotes/notes/skip-packages-env-c97e7b4820f9bfda.yaml View File

@@ -0,0 +1,7 @@
1
+---
2
+features:
3
+  - |
4
+    The `package-installs` element now supports skipping installation
5
+    of packages based on an environment variable specified in the
6
+    config file.  See the `package-installs` element documentation for
7
+    full details.

Loading…
Cancel
Save