github.com/argoproj/argo-events@v1.9.1/sensors/dependencies/transform.go (about) 1 package dependencies 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "time" 7 8 "github.com/argoproj/argo-events/pkg/apis/sensor/v1alpha1" 9 cloudevents "github.com/cloudevents/sdk-go/v2" 10 "github.com/itchyny/gojq" 11 "github.com/tidwall/gjson" 12 lua "github.com/yuin/gopher-lua" 13 ) 14 15 func ApplyTransform(event *cloudevents.Event, transform *v1alpha1.EventDependencyTransformer) (*cloudevents.Event, error) { 16 if transform == nil { 17 return event, nil 18 } 19 if transform.JQ != "" { 20 return applyJQTransform(event, transform.JQ) 21 } 22 if transform.Script != "" { 23 return applyScriptTransform(event, transform.Script) 24 } 25 return event, nil 26 } 27 28 func applyJQTransform(event *cloudevents.Event, command string) (*cloudevents.Event, error) { 29 if event == nil { 30 return nil, fmt.Errorf("nil Event") 31 } 32 payload := event.Data() 33 if payload == nil { 34 return event, nil 35 } 36 var js *json.RawMessage 37 if err := json.Unmarshal(payload, &js); err != nil { 38 return nil, err 39 } 40 var jsData []byte 41 jsData, err := json.Marshal(js) 42 if err != nil { 43 return nil, err 44 } 45 query, err := gojq.Parse(command) 46 if err != nil { 47 return nil, err 48 } 49 var temp map[string]interface{} 50 if err = json.Unmarshal(jsData, &temp); err != nil { 51 return nil, err 52 } 53 iter := query.Run(temp) 54 v, ok := iter.Next() 55 if !ok { 56 return nil, fmt.Errorf("no output available from the jq command execution") 57 } 58 switch v.(type) { 59 case map[string]interface{}: 60 resultContent, err := json.Marshal(v) 61 if err != nil { 62 return nil, err 63 } 64 if !gjson.ValidBytes(resultContent) { 65 return nil, fmt.Errorf("jq transformation output is not a JSON object") 66 } 67 if err = event.SetData(cloudevents.ApplicationJSON, resultContent); err != nil { 68 return nil, err 69 } 70 return event, nil 71 default: 72 return nil, fmt.Errorf("jq transformation output must be a JSON object") 73 } 74 } 75 76 func applyScriptTransform(event *cloudevents.Event, script string) (*cloudevents.Event, error) { 77 l := lua.NewState() 78 defer l.Close() 79 payload := event.Data() 80 if payload == nil { 81 return event, nil 82 } 83 var js *json.RawMessage 84 if err := json.Unmarshal(payload, &js); err != nil { 85 return nil, err 86 } 87 var jsData []byte 88 jsData, err := json.Marshal(js) 89 if err != nil { 90 return nil, err 91 } 92 var payloadJson map[string]interface{} 93 if err = json.Unmarshal(jsData, &payloadJson); err != nil { 94 return nil, err 95 } 96 lEvent := mapToTable(payloadJson) 97 l.SetGlobal("event", lEvent) 98 if err = l.DoString(script); err != nil { 99 return nil, err 100 } 101 lv := l.Get(-1) 102 tbl, ok := lv.(*lua.LTable) 103 if !ok { 104 return nil, fmt.Errorf("transformation script output type is not of lua table") 105 } 106 result := toGoValue(tbl) 107 resultJson, err := json.Marshal(result) 108 if err != nil { 109 return nil, err 110 } 111 if !gjson.Valid(string(resultJson)) { 112 return nil, fmt.Errorf("script transformation output is not a JSON object") 113 } 114 if err := event.SetData(cloudevents.ApplicationJSON, resultJson); err != nil { 115 return nil, err 116 } 117 return event, nil 118 } 119 120 // MapToTable converts a Go map to a lua table 121 func mapToTable(m map[string]interface{}) *lua.LTable { 122 resultTable := &lua.LTable{} 123 for key, element := range m { 124 switch t := element.(type) { 125 case float64: 126 resultTable.RawSetString(key, lua.LNumber(t)) 127 case int64: 128 resultTable.RawSetString(key, lua.LNumber(t)) 129 case string: 130 resultTable.RawSetString(key, lua.LString(t)) 131 case bool: 132 resultTable.RawSetString(key, lua.LBool(t)) 133 case []byte: 134 resultTable.RawSetString(key, lua.LString(string(t))) 135 case map[string]interface{}: 136 table := mapToTable(element.(map[string]interface{})) 137 resultTable.RawSetString(key, table) 138 case time.Time: 139 resultTable.RawSetString(key, lua.LNumber(t.Unix())) 140 case []map[string]interface{}: 141 sliceTable := &lua.LTable{} 142 for _, s := range element.([]map[string]interface{}) { 143 table := mapToTable(s) 144 sliceTable.Append(table) 145 } 146 resultTable.RawSetString(key, sliceTable) 147 case []interface{}: 148 sliceTable := &lua.LTable{} 149 for _, s := range element.([]interface{}) { 150 switch tt := s.(type) { 151 case map[string]interface{}: 152 t := mapToTable(s.(map[string]interface{})) 153 sliceTable.Append(t) 154 case float64: 155 sliceTable.Append(lua.LNumber(tt)) 156 case string: 157 sliceTable.Append(lua.LString(tt)) 158 case bool: 159 sliceTable.Append(lua.LBool(tt)) 160 } 161 } 162 resultTable.RawSetString(key, sliceTable) 163 default: 164 } 165 } 166 return resultTable 167 } 168 169 // toGoValue converts the given LValue to a Go object. 170 func toGoValue(lv lua.LValue) interface{} { 171 switch v := lv.(type) { 172 case *lua.LNilType: 173 return nil 174 case lua.LBool: 175 return bool(v) 176 case lua.LString: 177 return string(v) 178 case lua.LNumber: 179 return float64(v) 180 case *lua.LTable: 181 maxn := v.MaxN() 182 if maxn == 0 { // table 183 ret := make(map[string]interface{}) 184 v.ForEach(func(key, value lua.LValue) { 185 keystr := key.String() 186 ret[keystr] = toGoValue(value) 187 }) 188 return ret 189 } else { // array 190 ret := make([]interface{}, 0, maxn) 191 for i := 1; i <= maxn; i++ { 192 ret = append(ret, toGoValue(v.RawGetInt(i))) 193 } 194 return ret 195 } 196 default: 197 return v 198 } 199 }