github.com/kubeshop/testkube@v1.17.23/cmd/tcl/testworkflow-init/data/state.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 data 10 11 import ( 12 "bytes" 13 "encoding/gob" 14 "encoding/json" 15 "fmt" 16 "os" 17 "path/filepath" 18 "sync" 19 20 "github.com/kubeshop/testkube/pkg/tcl/expressionstcl" 21 ) 22 23 const ( 24 defaultInternalPath = "/.tktw" 25 defaultTerminationLogPath = "/dev/termination-log" 26 ) 27 28 type state struct { 29 Status TestWorkflowStatus `json:"status"` 30 Steps map[string]*StepInfo `json:"steps"` 31 Output map[string]string `json:"output"` 32 } 33 34 var State = &state{ 35 Steps: map[string]*StepInfo{}, 36 Output: map[string]string{}, 37 } 38 39 func (s *state) GetStep(ref string) *StepInfo { 40 _, ok := State.Steps[ref] 41 if !ok { 42 State.Steps[ref] = &StepInfo{Ref: ref} 43 } 44 return State.Steps[ref] 45 } 46 47 func (s *state) GetOutput(name string) (expressionstcl.Expression, bool, error) { 48 v, ok := s.Output[name] 49 if !ok { 50 return expressionstcl.None, false, nil 51 } 52 expr, err := expressionstcl.Compile(v) 53 return expr, true, err 54 } 55 56 func (s *state) SetOutput(ref, name string, value interface{}) { 57 if s.Output == nil { 58 s.Output = make(map[string]string) 59 } 60 v, err := json.Marshal(value) 61 if err == nil { 62 s.Output[name] = string(v) 63 } else { 64 fmt.Printf("Warning: couldn't save '%s' (%s) output: %s\n", name, ref, err.Error()) 65 } 66 } 67 68 func (s *state) GetSelfStatus() string { 69 if Step.Executed { 70 return string(Step.Status) 71 } 72 v := s.GetStep(Step.Ref) 73 if v.Status != StepStatusPassed { 74 return string(v.Status) 75 } 76 return string(Step.Status) 77 } 78 79 func (s *state) GetStatus() string { 80 if Step.Executed { 81 return string(Step.Status) 82 } 83 if Step.InitStatus == "" { 84 return string(s.Status) 85 } 86 v, err := RefStatusExpression(Step.InitStatus) 87 if err != nil { 88 return string(s.Status) 89 } 90 str, _ := v.Static().StringValue() 91 if str == "" { 92 return string(s.Status) 93 } 94 return str 95 } 96 97 func readState(filePath string) { 98 b, err := os.ReadFile(filePath) 99 if err != nil { 100 if !os.IsNotExist(err) { 101 panic(err) 102 } 103 return 104 } 105 if len(b) == 0 { 106 return 107 } 108 err = gob.NewDecoder(bytes.NewBuffer(b)).Decode(&State) 109 if err != nil { 110 panic(err) 111 } 112 } 113 114 func persistState(filePath string) { 115 b := bytes.Buffer{} 116 err := gob.NewEncoder(&b).Encode(State) 117 if err != nil { 118 panic(err) 119 } 120 121 err = os.WriteFile(filePath, b.Bytes(), 0777) 122 if err != nil { 123 panic(err) 124 } 125 } 126 127 func recomputeStatuses() { 128 // Read current status 129 status := StepStatus(State.GetSelfStatus()) 130 131 // Update own status 132 State.GetStep(Step.Ref).SetStatus(status) 133 134 // Update expected failure statuses 135 Iterate(Config.Resulting, func(r Rule) bool { 136 v, err := RefSuccessExpression(r.Expr) 137 if err != nil { 138 return false 139 } 140 vv, _ := v.Static().BoolValue() 141 if !vv { 142 for _, ref := range r.Refs { 143 if ref == "" { 144 State.Status = TestWorkflowStatusFailed 145 } else { 146 State.GetStep(ref).SetStatus(StepStatusFailed) 147 } 148 } 149 } 150 return true 151 }) 152 } 153 154 func persistStatus(filePath string) { 155 // Persist container termination result 156 res := fmt.Sprintf(`%s,%d`, State.GetStep(Step.Ref).Status, Step.ExitCode) 157 err := os.WriteFile(filePath, []byte(res), 0755) 158 if err != nil { 159 panic(err) 160 } 161 } 162 163 var loadStateMu sync.Mutex 164 var loadedState bool 165 166 func LoadState() { 167 defer loadStateMu.Unlock() 168 loadStateMu.Lock() 169 if !loadedState { 170 readState(filepath.Join(defaultInternalPath, "state")) 171 loadedState = true 172 } 173 } 174 175 func Finish() { 176 // Persist step information and shared data 177 recomputeStatuses() 178 persistStatus(defaultTerminationLogPath) 179 persistState(filepath.Join(defaultInternalPath, "state")) 180 181 // Kill the sub-process 182 if Step.Cmd != nil && Step.Cmd.Process != nil { 183 _ = Step.Cmd.Process.Kill() 184 } 185 186 // Emit end hint to allow exporting the timestamp 187 PrintHint(Step.Ref, "end") 188 189 // The init process needs to finish with zero exit code, 190 // to continue with the next container. 191 os.Exit(0) 192 }