def convert_string_containing_expressions(cls, match, **kwargs): expr = match.group('expr') converter = ExpressionConverter.get_converter(expr) if converter: expr = converter.unwrap_expression(expr) expr = converter.convert_string(expr, **kwargs) expr = converter.wrap_expression(expr) return expr
def replace_immediately_referenced_variables(self, task_name, when_expr, publish_dict): # Check for context variable references that are used in the 'when' # expression variables_in_when = self.extract_context_variables(when_expr) variables_in_publish = set(publish_dict.keys()) immediately_referenced_variables = variables_in_when & variables_in_publish # Replace the context variable references in the 'when' # expression with their expression from the 'publish' block for variable in immediately_referenced_variables: # First off, make sure they are the same type of expression, # we don't want to inject a Jinja expression in the middle # of a YAQL expression when_expr_type = ExpressionConverter.expression_type(when_expr) publish_expr_type = ExpressionConverter.expression_type(publish_dict[variable]) if when_expr_type == publish_expr_type: # Grab the variable expression converter = ExpressionConverter.get_converter(publish_dict[variable]) unwrapped_expr = converter.unwrap_expression(publish_dict[variable]) # Replace double parentheses # ((ctx().variable)) -> (result().result['variable'] + 1) variable_reference_dp = r'\(\(ctx\(\).{var}\)\)'.format(var=variable) when_expr = re.sub(variable_reference_dp, "({})".format(unwrapped_expr), when_expr) # Don't add in parentheses if they already exist # (ctx().variable) -> (result().result['variable'] + 1) variable_reference_dp = r'\(ctx\(\).{var}\)'.format(var=variable) when_expr = re.sub(variable_reference_dp, "({})".format(unwrapped_expr), when_expr) # Keep surrounding context and add surrounding parentheses # (ctx().variable ... -> ((result().result['variable'] + 1) ... # ... ctx().variable) -> ... (result().result['variable'] + 1)) # ...ctx().variable... -> ...(result().result['variable'] + 1)... variable_reference_ep = r'(.)\bctx\(\).{var}\b(.)'.format(var=variable) when_expr = re.sub(variable_reference_ep, r"\1({})\2".format(unwrapped_expr), when_expr) # Keep double enclosing internal parentheses # (ctx(variable)) -> (result().result['variable'] + 1) variable_reference_eip = r'\(ctx\({var}\)\)'.format(var=variable) when_expr = re.sub(variable_reference_eip, "({})".format(unwrapped_expr), when_expr) else: warnings.warn("The transition \"{when_expr}\" in {task_name} " "references the '{variable}' context variable, " "which is published in the same transition. You " "will need to manually convert the {variable} " "expression in the transition." .format(when_expr=when_expr, task_name=task_name, variable=variable)), return when_expr
def convert_task_transition_expr(self, task_name, expression_list, publish, orquesta_expr): # group all complex expressions by their common expression # this way we can keep all of the transitions with the same # expressions in the same `when:` condition # # on-success: # - do_thing_a: "{{ _.x }}" # - do_thing_b: "{{ _.x }}" # - do_thing_c: "{{ not _.x }}" # # should produce the following in orquesta # # next: # - when: "{{ succeeded() and _.x }}" # do: # - do_thing_a # - do_thing_b # - when: "{{ succeeded() and not _.x }}" # do: # - do_thing_c transitions = [] for expr, task_list in six.iteritems(expression_list): expr_transition = ruamel.yaml.comments.CommentedMap() expr_converted = ExpressionConverter.convert(expr) # for some transitions (on-complete) the orquesta_expr may be empty # so only add it in, if it's necessary if orquesta_expr: converter = ExpressionConverter.get_converter(expr_converted) expr_converted = converter.unwrap_expression(expr_converted) o_expr = '{} and ({})'.format(orquesta_expr, expr_converted) o_expr = converter.wrap_expression(o_expr) else: o_expr = expr_converted expr_transition['when'] = o_expr if publish: converted_publish = ExpressionConverter.convert_dict(publish) expr_transition['publish'] = [{k: v} for k, v in converted_publish.items()] expr_transition['when'] = self.replace_immediately_referenced_variables(task_name, expr_transition['when'], converted_publish) expr_transition['do'] = task_list transitions.append(expr_transition) return transitions
def convert_with_items(self, m_task_spec, expr_converter): with_items = m_task_spec['with-items'] with_attr = { 'items': self.convert_with_items_expr(with_items, expr_converter), } if m_task_spec.get('concurrency'): concurrency_expr = m_task_spec['concurrency'] # Only try to convert the concurrency expression if it's a str if isinstance(concurrency_expr, six.string_types): converter = ExpressionConverter.get_converter(concurrency_expr) if converter: concurrency_expr = converter.unwrap_expression(concurrency_expr) concurrency_expr = converter.convert_string(concurrency_expr) concurrency_expr = converter.wrap_expression(concurrency_expr) with_attr['concurrency'] = concurrency_expr return with_attr
def convert_with_items_expr(self, expression, expr_converter): # Convert all with-items attributes # # with-items: # - i in <% $.yaql_data %> # # with-items: # - i in <% $.yaql_data %> # - j in <% $.more_data %> # - k in <% $.all_the_data %> # # with-items: i in <% $.yaql_data %> # concurrency: 2 # # with-items: i in {{ _.jinja_data }} # # with-items: i in [0, 1, 2, 3] # # should produce the following in orquesta # # with: # items: i in <% ctx().yaql_data %> # # with: # items: i, j, k in <% zip($.yaql_data, $.more_data, $.all_the_data) %> # # with: # items: i in <% $.yaql_data %> # concurrency: 2 # # with: # items: i in {{ ctx().jinja_data }} # # with: # items: i in <% [0, 1, 2, 3] %> converter = None var_list = [] expr_list = [] if isinstance(expression, list): expression_list = expression else: # Create a list with a single element expression_list = [expression] for expr_item in expression_list: m = WITH_ITEMS_EXPR_RGX.match(expr_item) if m: var = m.group('var') expr = m.group('expr') converter = ExpressionConverter.get_converter(expr) if converter: expr = converter.unwrap_expression(expr) expr = converter.convert_string(expr) var_list.append(var) expr_list.append(expr) else: raise NotImplementedError("Unrecognized with-items expression: '{}'". format(expr_item)) # If we have a list of expressions, we need to join them with commas # and feed that into 'zip()' # If we only have one expression in the list, we don't need to join # them or use 'zip()', we just use it directly if len(expr_list) > 1: expr_list_string = 'zip({expr})'.format(expr=', '.join(expr_list)) else: expr_list_string = expr_list[0] # Default to the global expression converter if we could not determine one converter = converter if converter else expr_converter # We need to save the list of expression variables for when we convert # item access to item() instead of ctx() self.task_with_item_vars.extend(var_list) return "{vars} in {wrapped_expr}".format( vars=', '.join(var_list), wrapped_expr=converter.wrap_expression(expr_list_string))
def convert_retry(self, m_retry, task_name): # Convert 'retry' specification # # retry: # count: 10 # delay: 20 # break-on: <% $.my_var = 'break' %> # # should produce the following in orquesta # # retry: # count: 10 # delay: 20 # when: <% failed() and not (ctx().my_var = 'break') %> # # (note that the 'when' expression is inverted/negated from break-on) # # and this: # # retry: # count: 10 # delay: 20 # continue-on: <% $.my_var = 'continue' %> # # should produce the following in orquesta # # retry: # count: 10 # delay: 20 # when: <% succeeded() and (ctx().my_var = 'continue') %> # o_retry = ruamel.yaml.comments.CommentedMap() if m_retry.get('count'): o_retry['count'] = m_retry['count'] if m_retry.get('delay'): o_retry['delay'] = m_retry['delay'] # Check for both and bail early with_continue = None with_break = None if m_retry.get('continue-on'): continue_expr = m_retry['continue-on'] continue_converter = ExpressionConverter.get_converter(continue_expr) if not continue_converter: raise NotImplementedError("Could not convert continue-on expression: {converter} " "in task '{task_name}'" .format(converter=continue_converter, task_name=task_name)) continue_expr = continue_converter.unwrap_expression(continue_expr) continue_expr = continue_converter.convert_string(continue_expr) with_continue = 'succeeded() and ({continue_expr})'.format(continue_expr=continue_expr) if m_retry.get('break-on'): break_expr = m_retry['break-on'] break_converter = ExpressionConverter.get_converter(break_expr) if not break_converter: raise NotImplementedError("Could not convert break-on expression: {converter} " "in task '{task_name}'" .format(converter=break_converter, task_name=task_name)) break_expr = break_converter.unwrap_expression(break_expr) break_expr = break_converter.convert_string(break_expr) with_break = 'failed() and not ({break_expr})'.format(break_expr=break_expr) if with_continue and with_break: # The converters are classes themselves if continue_converter is not break_converter: raise NotImplementedError("Cannot convert continue-on ({continue_on}) and " "break-on ({break_on}) expressions that are different " "types in task '{task_name}'" .format(continue_on=m_retry['continue-on'], break_on=m_retry['break-on'], task_name=task_name)) with_expr = ('({with_continue}) or ({with_break})' .format(with_continue=with_continue, with_break=with_break)) o_retry['when'] = continue_converter.wrap_expression(with_expr) elif m_retry.get('continue-on'): o_retry['when'] = continue_converter.wrap_expression(with_continue) elif m_retry.get('break-on'): o_retry['when'] = break_converter.wrap_expression(with_break) return o_retry
def test_get_converter_none(self): expr = "test" result = ExpressionConverter.get_converter(expr) self.assertIsNone(result)
def test_get_converter_yaql(self): expr = "<% $.test %>" result = ExpressionConverter.get_converter(expr) self.assertIs(result, YaqlExpressionConverter)
def test_get_converter_jinja(self): expr = "{{ _.test }}" result = ExpressionConverter.get_converter(expr) self.assertIs(result, JinjaExpressionConverter)