github.com/kubeshop/testkube@v1.17.23/pkg/tcl/testworkflowstcl/testworkflowprocessor/initprocess.go (about)

     1  // Copyright 2024 Testkube.
     2  //
     3  // Licensed as a Testkube Pro file under the Testkube Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //	https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt
     8  
     9  package testworkflowprocessor
    10  
    11  import (
    12  	"errors"
    13  	"fmt"
    14  	"maps"
    15  	"strconv"
    16  	"strings"
    17  
    18  	testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1"
    19  	"github.com/kubeshop/testkube/cmd/tcl/testworkflow-init/constants"
    20  	"github.com/kubeshop/testkube/internal/common"
    21  	"github.com/kubeshop/testkube/pkg/tcl/expressionstcl"
    22  	constants2 "github.com/kubeshop/testkube/pkg/tcl/testworkflowstcl/testworkflowprocessor/constants"
    23  )
    24  
    25  type initProcess struct {
    26  	ref        string
    27  	init       []string
    28  	params     []string
    29  	retry      map[string]testworkflowsv1.RetryPolicy
    30  	command    []string
    31  	args       []string
    32  	envs       []string
    33  	results    []string
    34  	conditions map[string][]string
    35  	negative   bool
    36  	errors     []error
    37  }
    38  
    39  func NewInitProcess() *initProcess {
    40  	return &initProcess{
    41  		conditions: map[string][]string{},
    42  		retry:      map[string]testworkflowsv1.RetryPolicy{},
    43  	}
    44  }
    45  
    46  func (p *initProcess) Error() error {
    47  	if len(p.errors) == 0 {
    48  		return nil
    49  	}
    50  	return errors.Join(p.errors...)
    51  }
    52  
    53  func (p *initProcess) SetRef(ref string) *initProcess {
    54  	p.ref = ref
    55  	return p
    56  }
    57  
    58  func (p *initProcess) Command() []string {
    59  	args := p.params
    60  
    61  	// TODO: Support nested retries
    62  	policy, ok := p.retry[p.ref]
    63  	if ok {
    64  		args = append(args, constants.ArgRetryCount, strconv.Itoa(int(policy.Count)), constants.ArgRetryUntil, expressionstcl.Escape(policy.Until))
    65  	}
    66  	if p.negative {
    67  		args = append(args, constants.ArgNegative, "true")
    68  	}
    69  	if len(p.init) > 0 {
    70  		args = append(args, constants.ArgInit, strings.Join(p.init, "&&"))
    71  	}
    72  	if len(p.envs) > 0 {
    73  		args = append(args, constants.ArgComputeEnv, strings.Join(p.envs, ","))
    74  	}
    75  	if len(p.conditions) > 0 {
    76  		for k, v := range p.conditions {
    77  			args = append(args, constants.ArgCondition, fmt.Sprintf("%s=%s", strings.Join(common.UniqueSlice(v), ","), k))
    78  		}
    79  	}
    80  	for _, r := range p.results {
    81  		args = append(args, constants.ArgResult, r)
    82  	}
    83  	return append([]string{constants2.DefaultInitPath, p.ref}, append(args, constants.ArgSeparator)...)
    84  }
    85  
    86  func (p *initProcess) Args() []string {
    87  	args := make([]string, 0)
    88  	if len(p.command) > 0 {
    89  		args = p.command
    90  	}
    91  	if len(p.command) > 0 || len(p.args) > 0 {
    92  		args = append(args, p.args...)
    93  	}
    94  	return args
    95  }
    96  
    97  func (p *initProcess) param(args ...string) *initProcess {
    98  	p.params = append(p.params, args...)
    99  	return p
   100  }
   101  
   102  func (p *initProcess) compile(expr ...string) []string {
   103  	for i, e := range expr {
   104  		res, err := expressionstcl.Compile(e)
   105  		if err == nil {
   106  			expr[i] = res.String()
   107  		} else {
   108  			p.errors = append(p.errors, fmt.Errorf("resolving expression: %s: %s", expr[i], err.Error()))
   109  		}
   110  	}
   111  	return expr
   112  }
   113  
   114  func (p *initProcess) SetCommand(command ...string) *initProcess {
   115  	p.command = command
   116  	return p
   117  }
   118  
   119  func (p *initProcess) SetArgs(args ...string) *initProcess {
   120  	p.args = args
   121  	return p
   122  }
   123  
   124  func (p *initProcess) AddTimeout(duration string, refs ...string) *initProcess {
   125  	return p.param(constants.ArgTimeout, fmt.Sprintf("%s=%s", strings.Join(refs, ","), duration))
   126  }
   127  
   128  func (p *initProcess) SetInitialStatus(expr ...string) *initProcess {
   129  	p.init = nil
   130  	for _, v := range p.compile(expr...) {
   131  		p.init = append(p.init, v)
   132  	}
   133  	return p
   134  }
   135  
   136  func (p *initProcess) PrependInitialStatus(expr ...string) *initProcess {
   137  	init := []string(nil)
   138  	for _, v := range p.compile(expr...) {
   139  		init = append(init, v)
   140  	}
   141  	p.init = append(init, p.init...)
   142  	return p
   143  }
   144  
   145  func (p *initProcess) AddComputedEnvs(names ...string) *initProcess {
   146  	p.envs = append(p.envs, names...)
   147  	return p
   148  }
   149  
   150  func (p *initProcess) SetNegative(negative bool) *initProcess {
   151  	p.negative = negative
   152  	return p
   153  }
   154  
   155  func (p *initProcess) AddResult(condition string, refs ...string) *initProcess {
   156  	if len(refs) == 0 || condition == "" {
   157  		return p
   158  	}
   159  	p.results = append(p.results, fmt.Sprintf("%s=%s", strings.Join(refs, ","), p.compile(condition)[0]))
   160  	return p
   161  }
   162  
   163  func (p *initProcess) ResetResults() *initProcess {
   164  	p.results = nil
   165  	return p
   166  }
   167  
   168  func (p *initProcess) AddCondition(condition string, refs ...string) *initProcess {
   169  	if len(refs) == 0 || condition == "" {
   170  		return p
   171  	}
   172  	expr := p.compile(condition)[0]
   173  	p.conditions[expr] = append(p.conditions[expr], refs...)
   174  	return p
   175  }
   176  
   177  func (p *initProcess) ResetCondition() *initProcess {
   178  	p.conditions = make(map[string][]string)
   179  	return p
   180  }
   181  
   182  func (p *initProcess) AddRetryPolicy(policy testworkflowsv1.RetryPolicy, ref string) *initProcess {
   183  	if policy.Count <= 0 {
   184  		delete(p.retry, ref)
   185  		return p
   186  	}
   187  	until := policy.Until
   188  	if until == "" {
   189  		until = "passed"
   190  	}
   191  	p.retry[ref] = testworkflowsv1.RetryPolicy{Count: policy.Count, Until: until}
   192  	return p
   193  }
   194  
   195  func (p *initProcess) Children(ref string) *initProcess {
   196  	return &initProcess{
   197  		ref:        ref,
   198  		params:     p.params,
   199  		retry:      maps.Clone(p.retry),
   200  		command:    p.command,
   201  		args:       p.args,
   202  		init:       p.init,
   203  		envs:       p.envs,
   204  		results:    p.results,
   205  		conditions: maps.Clone(p.conditions),
   206  		negative:   p.negative,
   207  		errors:     p.errors,
   208  	}
   209  }