xref: /aosp_15_r20/external/autotest/client/common_lib/config_vars.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright (c) 2021 The Chromium Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li"""
6*9c5db199SXin LiFunctions to load config variables from JSON with transformation.
7*9c5db199SXin Li
8*9c5db199SXin Li* The config is a key-value dictionary.
9*9c5db199SXin Li* If the value is a list, then the list constitutes a list of conditions
10*9c5db199SXin Li  to check.
11*9c5db199SXin Li* A condition is a key-value dictionary where the key is an external variable
12*9c5db199SXin Li  name and the value is a case-insensitive regexp to match. If multiple
13*9c5db199SXin Li  variables used, they all must match for the condition to succeed.
14*9c5db199SXin Li* A special key "value" is the value to assign if condition succeeds.
15*9c5db199SXin Li* The first matching condition wins.
16*9c5db199SXin Li* Condition with zero external vars always succeeds - it should be the last in
17*9c5db199SXin Li  the list as a last resort case.
18*9c5db199SXin Li* If none of conditions match, it's an error.
19*9c5db199SXin Li* The value, in turn, can be a nested list of conditions.
20*9c5db199SXin Li* If the value is a boolean, the condition checks for the presence or absence
21*9c5db199SXin Li  of an external variable.
22*9c5db199SXin Li
23*9c5db199SXin LiExample:
24*9c5db199SXin Li    Python source:
25*9c5db199SXin Li        config = TransformJsonFile(
26*9c5db199SXin Li                                    "config.json",
27*9c5db199SXin Li                                    extvars={
28*9c5db199SXin Li                                        "board": "board1",
29*9c5db199SXin Li                                        "model": "model1",
30*9c5db199SXin Li                                    })
31*9c5db199SXin Li        # config -> {
32*9c5db199SXin Li        #               "cuj_username": "user",
33*9c5db199SXin Li        #               "private_key": "SECRET",
34*9c5db199SXin Li        #               "some_var": "val for board1",
35*9c5db199SXin Li        #               "some_var2": "default val2",
36*9c5db199SXin Li        #           }
37*9c5db199SXin Li
38*9c5db199SXin Li        config = TransformJsonFile(
39*9c5db199SXin Li                                    "config.json",
40*9c5db199SXin Li                                    extvars={
41*9c5db199SXin Li                                        "board": "board2",
42*9c5db199SXin Li                                        "model": "model2",
43*9c5db199SXin Li                                    })
44*9c5db199SXin Li        # config -> {
45*9c5db199SXin Li        #               "cuj_username": "user",
46*9c5db199SXin Li        #               "private_key": "SECRET",
47*9c5db199SXin Li        #               "some_var": "val for board2",
48*9c5db199SXin Li        #               "some_var2": "val2 for board2 model2",
49*9c5db199SXin Li        #           }
50*9c5db199SXin Li
51*9c5db199SXin Li    config.json:
52*9c5db199SXin Li        {
53*9c5db199SXin Li            "cuj_username": "user",
54*9c5db199SXin Li            "private_key": "SECRET",
55*9c5db199SXin Li            "some_var": [
56*9c5db199SXin Li                {
57*9c5db199SXin Li                    "board": "board1.*",
58*9c5db199SXin Li                    "value": "val for board1",
59*9c5db199SXin Li                },
60*9c5db199SXin Li                {
61*9c5db199SXin Li                    "board": "board2.*",
62*9c5db199SXin Li                    "value": "val for board2",
63*9c5db199SXin Li                },
64*9c5db199SXin Li                {
65*9c5db199SXin Li                    "value": "default val",
66*9c5db199SXin Li                }
67*9c5db199SXin Li            ],
68*9c5db199SXin Li            "some_var2": [
69*9c5db199SXin Li                {
70*9c5db199SXin Li                    "board": "board2.*",
71*9c5db199SXin Li                    "model": "model2.*",
72*9c5db199SXin Li                    "value": "val2 for board2 model2",
73*9c5db199SXin Li                },
74*9c5db199SXin Li                {
75*9c5db199SXin Li                    "value": "default val2",
76*9c5db199SXin Li                }
77*9c5db199SXin Li            ],
78*9c5db199SXin Li        }
79*9c5db199SXin Li
80*9c5db199SXin LiSee more examples in config_vars_unittest.py
81*9c5db199SXin Li
82*9c5db199SXin Li"""
83*9c5db199SXin Li
84*9c5db199SXin Li# Lint as: python2, python3
85*9c5db199SXin Li# pylint: disable=missing-docstring,bad-indentation
86*9c5db199SXin Lifrom __future__ import absolute_import
87*9c5db199SXin Lifrom __future__ import division
88*9c5db199SXin Lifrom __future__ import print_function
89*9c5db199SXin Li
90*9c5db199SXin Liimport json
91*9c5db199SXin Liimport logging
92*9c5db199SXin Liimport re
93*9c5db199SXin Li
94*9c5db199SXin Litry:
95*9c5db199SXin Li    unicode
96*9c5db199SXin Liexcept NameError:
97*9c5db199SXin Li    unicode = str
98*9c5db199SXin Li
99*9c5db199SXin LiVERBOSE = False
100*9c5db199SXin Li
101*9c5db199SXin Li
102*9c5db199SXin Liclass ConfigTransformError(ValueError):
103*9c5db199SXin Li    pass
104*9c5db199SXin Li
105*9c5db199SXin Li
106*9c5db199SXin Lidef TransformConfig(data, extvars):
107*9c5db199SXin Li    """Transforms data loaded from JSON to config variables.
108*9c5db199SXin Li
109*9c5db199SXin Li    Args:
110*9c5db199SXin Li        data (dict): input data dictionary from JSON parser
111*9c5db199SXin Li        extvars (dict): external variables dictionary
112*9c5db199SXin Li
113*9c5db199SXin Li    Returns:
114*9c5db199SXin Li        dict: config variables
115*9c5db199SXin Li
116*9c5db199SXin Li    Raises:
117*9c5db199SXin Li        ConfigTransformError: transformation error
118*9c5db199SXin Li        're' errors
119*9c5db199SXin Li    """
120*9c5db199SXin Li    if not isinstance(data, dict):
121*9c5db199SXin Li        _Error('Top level configuration object must be a dictionary but got ' +
122*9c5db199SXin Li               data.__class__.__name__)
123*9c5db199SXin Li
124*9c5db199SXin Li    return {key: _GetVal(key, val, extvars) for key, val in data.items()}
125*9c5db199SXin Li
126*9c5db199SXin Li
127*9c5db199SXin Lidef TransformJsonText(text, extvars):
128*9c5db199SXin Li    """Transforms JSON text to config variables.
129*9c5db199SXin Li
130*9c5db199SXin Li    Args:
131*9c5db199SXin Li        text (str): JSON input
132*9c5db199SXin Li        extvars (dict): external variables dictionary
133*9c5db199SXin Li
134*9c5db199SXin Li    Returns:
135*9c5db199SXin Li        dict: config variables
136*9c5db199SXin Li
137*9c5db199SXin Li    Raises:
138*9c5db199SXin Li        ConfigTransformError: transformation error
139*9c5db199SXin Li        're' errors
140*9c5db199SXin Li        'json' errors
141*9c5db199SXin Li    """
142*9c5db199SXin Li    data = json.loads(text)
143*9c5db199SXin Li    return TransformConfig(data, extvars)
144*9c5db199SXin Li
145*9c5db199SXin Li
146*9c5db199SXin Lidef TransformJsonFile(file_name, extvars):
147*9c5db199SXin Li    """Transforms JSON file to config variables.
148*9c5db199SXin Li
149*9c5db199SXin Li    Args:
150*9c5db199SXin Li        file_name (str): JSON file name
151*9c5db199SXin Li        extvars (dict): external variables dictionary
152*9c5db199SXin Li
153*9c5db199SXin Li    Returns:
154*9c5db199SXin Li        dict: config variables
155*9c5db199SXin Li
156*9c5db199SXin Li    Raises:
157*9c5db199SXin Li        ConfigTransformError: transformation error
158*9c5db199SXin Li        're' errors
159*9c5db199SXin Li        'json' errors
160*9c5db199SXin Li        IO errors
161*9c5db199SXin Li    """
162*9c5db199SXin Li    with open(file_name, 'r') as f:
163*9c5db199SXin Li        data = json.load(f)
164*9c5db199SXin Li    return TransformConfig(data, extvars)
165*9c5db199SXin Li
166*9c5db199SXin Li
167*9c5db199SXin Lidef _GetVal(key, val, extvars):
168*9c5db199SXin Li    """Calculates and returns the config variable value.
169*9c5db199SXin Li
170*9c5db199SXin Li    Args:
171*9c5db199SXin Li        key (str): key for error reporting
172*9c5db199SXin Li        val (str | list): variable value or conditions list
173*9c5db199SXin Li        extvars (dict): external variables dictionary
174*9c5db199SXin Li
175*9c5db199SXin Li    Returns:
176*9c5db199SXin Li        str: resolved variable value
177*9c5db199SXin Li
178*9c5db199SXin Li    Raises:
179*9c5db199SXin Li        ConfigTransformError: transformation error
180*9c5db199SXin Li    """
181*9c5db199SXin Li    if (isinstance(val, str) or isinstance(val, unicode)
182*9c5db199SXin Li                or isinstance(val, int) or isinstance(val, float)):
183*9c5db199SXin Li        return val
184*9c5db199SXin Li
185*9c5db199SXin Li    if not isinstance(val, list):
186*9c5db199SXin Li        _Error('Conditions must be an array but got ' + val.__class__.__name__,
187*9c5db199SXin Li               json.dumps(val), key)
188*9c5db199SXin Li
189*9c5db199SXin Li    for cond in val:
190*9c5db199SXin Li        if not isinstance(cond, dict):
191*9c5db199SXin Li            _Error(
192*9c5db199SXin Li                    'Condition must be a dictionary but got ' +
193*9c5db199SXin Li                    cond.__class__.__name__, json.dumps(cond), key)
194*9c5db199SXin Li        if 'value' not in cond:
195*9c5db199SXin Li            _Error('Missing mandatory "value" key from condition',
196*9c5db199SXin Li                   json.dumps(cond), key)
197*9c5db199SXin Li
198*9c5db199SXin Li        for cond_key, cond_val in cond.items():
199*9c5db199SXin Li            if cond_key == 'value':
200*9c5db199SXin Li                continue
201*9c5db199SXin Li
202*9c5db199SXin Li            if isinstance(cond_val, bool):
203*9c5db199SXin Li                # Boolean value -> check if variable exists
204*9c5db199SXin Li                if (cond_key in extvars) == cond_val:
205*9c5db199SXin Li                    continue
206*9c5db199SXin Li                else:
207*9c5db199SXin Li                    break
208*9c5db199SXin Li
209*9c5db199SXin Li            if cond_key not in extvars:
210*9c5db199SXin Li                logging.warning('Unknown external var: %s', cond_key)
211*9c5db199SXin Li                break
212*9c5db199SXin Li            if re.search(cond_val, extvars[cond_key], re.I) is None:
213*9c5db199SXin Li                break
214*9c5db199SXin Li        else:
215*9c5db199SXin Li            return _GetVal(key, cond['value'], extvars)
216*9c5db199SXin Li
217*9c5db199SXin Li    _Error('Condition did not match any external vars',
218*9c5db199SXin Li           json.dumps(val, indent=4) + '\nvars: ' + extvars.__str__(), key)
219*9c5db199SXin Li
220*9c5db199SXin Li
221*9c5db199SXin Lidef _Error(text, extra='', key=''):
222*9c5db199SXin Li    """Reports and raises an error.
223*9c5db199SXin Li
224*9c5db199SXin Li    Args:
225*9c5db199SXin Li        text (str): Error text
226*9c5db199SXin Li        extra (str, optional): potentially sensitive error text for verbose output
227*9c5db199SXin Li        key (str): key for error reporting or empty string if none
228*9c5db199SXin Li
229*9c5db199SXin Li    Raises:
230*9c5db199SXin Li        ConfigTransformError: error
231*9c5db199SXin Li    """
232*9c5db199SXin Li    if key:
233*9c5db199SXin Li        text = key + ': ' + text
234*9c5db199SXin Li    if VERBOSE and extra:
235*9c5db199SXin Li        text += ':\n' + extra
236*9c5db199SXin Li    logging.error('%s', text)
237*9c5db199SXin Li    raise ConfigTransformError(text)
238