github.com/kubeshop/testkube@v1.17.23/cmd/tcl/testworkflow-init/main.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 main 10 11 import ( 12 "fmt" 13 "os" 14 "os/signal" 15 "slices" 16 "strings" 17 "syscall" 18 "time" 19 20 "github.com/kballard/go-shellquote" 21 22 "github.com/kubeshop/testkube/cmd/tcl/testworkflow-init/constants" 23 "github.com/kubeshop/testkube/cmd/tcl/testworkflow-init/data" 24 "github.com/kubeshop/testkube/cmd/tcl/testworkflow-init/output" 25 "github.com/kubeshop/testkube/cmd/tcl/testworkflow-init/run" 26 ) 27 28 func main() { 29 if len(os.Args) < 2 { 30 output.Failf(output.CodeInputError, "missing step reference") 31 } 32 data.Step.Ref = os.Args[1] 33 34 now := time.Now() 35 36 // Load shared state 37 data.LoadState() 38 39 // Initialize space for parsing args 40 config := map[string]string{} 41 computed := []string(nil) 42 conditions := []data.Rule(nil) 43 resulting := []data.Rule(nil) 44 timeouts := []data.Timeout(nil) 45 args := []string(nil) 46 47 // Read arguments into the base data 48 for i := 2; i < len(os.Args); i += 2 { 49 if i+1 == len(os.Args) { 50 break 51 } 52 switch os.Args[i] { 53 case constants.ArgSeparator: 54 args = os.Args[i+1:] 55 i = len(os.Args) 56 case constants.ArgInit, constants.ArgInitLong: 57 data.Step.InitStatus = os.Args[i+1] 58 case constants.ArgCondition, constants.ArgConditionLong: 59 v := strings.SplitN(os.Args[i+1], "=", 2) 60 refs := strings.Split(v[0], ",") 61 if len(v) == 2 { 62 conditions = append(conditions, data.Rule{Expr: v[1], Refs: refs}) 63 } else { 64 conditions = append(conditions, data.Rule{Expr: "true", Refs: refs}) 65 } 66 case constants.ArgResult, constants.ArgResultLong: 67 v := strings.SplitN(os.Args[i+1], "=", 2) 68 refs := strings.Split(v[0], ",") 69 if len(v) == 2 { 70 resulting = append(resulting, data.Rule{Expr: v[1], Refs: refs}) 71 } else { 72 resulting = append(resulting, data.Rule{Expr: "true", Refs: refs}) 73 } 74 case constants.ArgTimeout, constants.ArgTimeoutLong: 75 v := strings.SplitN(os.Args[i+1], "=", 2) 76 if len(v) == 2 { 77 timeouts = append(timeouts, data.Timeout{Ref: v[0], Duration: v[1]}) 78 } else { 79 timeouts = append(timeouts, data.Timeout{Ref: v[0], Duration: ""}) 80 } 81 case constants.ArgComputeEnv, constants.ArgComputeEnvLong: 82 computed = append(computed, strings.Split(os.Args[i+1], ",")...) 83 case constants.ArgNegative, constants.ArgNegativeLong: 84 config["negative"] = os.Args[i+1] 85 case constants.ArgRetryCount: 86 config["retryCount"] = os.Args[i+1] 87 case constants.ArgRetryUntil: 88 config["retryUntil"] = os.Args[i+1] 89 case constants.ArgDebug: 90 config["debug"] = os.Args[i+1] 91 default: 92 output.Failf(output.CodeInputError, "unknown parameter: %s", os.Args[i]) 93 } 94 } 95 96 // Compute environment variables 97 for _, name := range computed { 98 initial := os.Getenv(name) 99 value, err := data.Template(initial) 100 if err != nil { 101 output.Failf(output.CodeInputError, `resolving "%s" environment variable: %s: %s`, name, initial, err.Error()) 102 } 103 _ = os.Setenv(name, value) 104 } 105 106 // Compute conditional steps - ignore errors initially, as the may be dependent on themselves 107 data.Iterate(conditions, func(c data.Rule) bool { 108 expr, err := data.Expression(c.Expr) 109 if err != nil { 110 return false 111 } 112 v, _ := expr.BoolValue() 113 if !v { 114 for _, r := range c.Refs { 115 data.State.GetStep(r).Skip(now) 116 } 117 } 118 return true 119 }) 120 121 // Fail invalid conditional steps 122 for _, c := range conditions { 123 _, err := data.Expression(c.Expr) 124 if err != nil { 125 output.Failf(output.CodeInputError, "broken condition for refs: %s: %s: %s", strings.Join(c.Refs, ", "), c.Expr, err.Error()) 126 } 127 } 128 129 // Start all acknowledged steps 130 for _, f := range resulting { 131 for _, r := range f.Refs { 132 if r != "" { 133 data.State.GetStep(r).Start(now) 134 } 135 } 136 } 137 for _, t := range timeouts { 138 if t.Ref != "" { 139 data.State.GetStep(t.Ref).Start(now) 140 } 141 } 142 data.State.GetStep(data.Step.Ref).Start(now) 143 144 // Register timeouts 145 for _, t := range timeouts { 146 err := data.State.GetStep(t.Ref).SetTimeoutDuration(now, t.Duration) 147 if err != nil { 148 output.Failf(output.CodeInputError, "broken timeout for ref: %s: %s: %s", t.Ref, t.Duration, err.Error()) 149 } 150 } 151 152 // Save the resulting conditions 153 data.Config.Resulting = resulting 154 155 // Don't call further if the step is already skipped 156 if data.State.GetStep(data.Step.Ref).Status == data.StepStatusSkipped { 157 if data.Config.Debug { 158 fmt.Printf("Skipped.\n") 159 } 160 data.Finish() 161 } 162 163 // Load the rest of the configuration 164 var err error 165 for k, v := range config { 166 config[k], err = data.Template(v) 167 if err != nil { 168 output.Failf(output.CodeInputError, `resolving "%s" param: %s: %s`, k, v, err.Error()) 169 } 170 } 171 data.LoadConfig(config) 172 173 // Compute templates in the cmd/args 174 original := slices.Clone(args) 175 for i := range args { 176 args[i], err = data.Template(args[i]) 177 if err != nil { 178 output.Failf(output.CodeInputError, `resolving command: %s: %s`, shellquote.Join(original...), err.Error()) 179 } 180 } 181 182 // Fail when there is nothing to run 183 if len(args) == 0 { 184 output.Failf(output.CodeNoCommand, "missing command to run") 185 } 186 187 // Handle aborting 188 stopSignal := make(chan os.Signal, 1) 189 signal.Notify(stopSignal, syscall.SIGINT, syscall.SIGTERM) 190 go func() { 191 <-stopSignal 192 fmt.Println("The task was aborted.") 193 data.Step.Status = data.StepStatusAborted 194 data.Step.ExitCode = output.CodeAborted 195 data.Finish() 196 }() 197 198 // Handle timeouts 199 for _, t := range timeouts { 200 go func(ref string) { 201 time.Sleep(data.State.GetStep(ref).TimeoutAt.Sub(time.Now())) 202 fmt.Printf("Timed out.\n") 203 data.State.GetStep(ref).SetStatus(data.StepStatusTimeout) 204 data.Step.Status = data.StepStatusTimeout 205 data.Step.ExitCode = output.CodeTimeout 206 data.Finish() 207 }(t.Ref) 208 } 209 210 // Start the task 211 data.Step.Executed = true 212 run.Run(args[0], args[1:]) 213 214 os.Exit(0) 215 }