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  }