github.com/argoproj/argo-events@v1.9.1/sensors/triggers/params.go (about) 1 /* 2 Copyright 2018 BlackRock, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package triggers 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "text/template" 24 25 sprig "github.com/Masterminds/sprig/v3" 26 "github.com/ghodss/yaml" 27 "github.com/tidwall/gjson" 28 "github.com/tidwall/sjson" 29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 31 "github.com/argoproj/argo-events/common" 32 "github.com/argoproj/argo-events/pkg/apis/sensor/v1alpha1" 33 ) 34 35 const ( 36 jsonType = "JSON" 37 stringType = "String" 38 ) 39 40 // ConstructPayload constructs a payload for operations involving request and responses like HTTP request. 41 func ConstructPayload(events map[string]*v1alpha1.Event, parameters []v1alpha1.TriggerParameter) ([]byte, error) { 42 var payload []byte 43 44 for _, parameter := range parameters { 45 value, typ, err := ResolveParamValue(parameter.Src, events) 46 if err != nil { 47 return nil, err 48 } 49 if typ != stringType && parameter.Src.UseRawData { 50 tmp, err := sjson.SetRawBytes(payload, parameter.Dest, []byte(*value)) 51 if err != nil { 52 return nil, err 53 } 54 payload = tmp 55 } else { 56 tmp, err := sjson.SetBytes(payload, parameter.Dest, *value) 57 if err != nil { 58 return nil, err 59 } 60 payload = tmp 61 } 62 } 63 return payload, nil 64 } 65 66 // ApplyTemplateParameters applies parameters to trigger template 67 func ApplyTemplateParameters(events map[string]*v1alpha1.Event, trigger *v1alpha1.Trigger) error { 68 if trigger.Parameters != nil && len(trigger.Parameters) > 0 { 69 templateBytes, err := json.Marshal(trigger.Template) 70 if err != nil { 71 return err 72 } 73 tObj, err := ApplyParams(templateBytes, trigger.Parameters, events) 74 if err != nil { 75 return err 76 } 77 tmplt := &v1alpha1.TriggerTemplate{} 78 if err = json.Unmarshal(tObj, tmplt); err != nil { 79 return err 80 } 81 trigger.Template = tmplt 82 } 83 return nil 84 } 85 86 // ApplyResourceParameters applies parameters to K8s resource within trigger 87 func ApplyResourceParameters(events map[string]*v1alpha1.Event, parameters []v1alpha1.TriggerParameter, obj *unstructured.Unstructured) error { 88 if parameters != nil { 89 jObj, err := obj.MarshalJSON() 90 if err != nil { 91 return err 92 } 93 jUpdatedObj, err := ApplyParams(jObj, parameters, events) 94 if err != nil { 95 return err 96 } 97 err = obj.UnmarshalJSON(jUpdatedObj) 98 if err != nil { 99 return err 100 } 101 } 102 return nil 103 } 104 105 // ApplyParams applies the params to the resource json object 106 func ApplyParams(jsonObj []byte, params []v1alpha1.TriggerParameter, events map[string]*v1alpha1.Event) ([]byte, error) { 107 for _, param := range params { 108 // let's grab the param value 109 value, typ, err := ResolveParamValue(param.Src, events) 110 if err != nil { 111 return nil, err 112 } 113 if value == nil { 114 continue 115 } 116 117 switch op := param.Operation; op { 118 case v1alpha1.TriggerParameterOpAppend, v1alpha1.TriggerParameterOpPrepend: 119 // prepend or append the current value 120 current := gjson.GetBytes(jsonObj, param.Dest) 121 122 if current.Exists() { 123 typ = stringType 124 if op == v1alpha1.TriggerParameterOpAppend { 125 *value = current.String() + *value 126 } else { 127 *value += current.String() 128 } 129 } 130 case v1alpha1.TriggerParameterOpOverwrite, v1alpha1.TriggerParameterOpNone: 131 // simply overwrite the current value with the new one 132 default: 133 return nil, fmt.Errorf("unsupported trigger parameter operation: %+v", op) 134 } 135 // now let's set the value 136 if typ != stringType && param.Src.UseRawData { 137 tmp, err := sjson.SetRawBytes(jsonObj, param.Dest, []byte(*value)) 138 if err != nil { 139 return nil, err 140 } 141 jsonObj = tmp 142 } else { 143 tmp, err := sjson.SetBytes(jsonObj, param.Dest, *value) 144 if err != nil { 145 return nil, err 146 } 147 jsonObj = tmp 148 } 149 } 150 return jsonObj, nil 151 } 152 153 func isJSON(b []byte) bool { 154 var js json.RawMessage 155 return json.Unmarshal(b, &js) == nil 156 } 157 158 // util method to render an event's data as a JSON []byte 159 // json is a subset of yaml so this should work... 160 func renderEventDataAsJSON(event *v1alpha1.Event) ([]byte, error) { 161 if event == nil { 162 return nil, fmt.Errorf("event is nil") 163 } 164 raw := event.Data 165 switch event.Context.DataContentType { 166 case common.MediaTypeJSON: 167 if isJSON(raw) { 168 return raw, nil 169 } 170 return nil, fmt.Errorf("event data is not valid JSON") 171 case common.MediaTypeYAML: 172 data, err := yaml.YAMLToJSON(raw) 173 if err != nil { 174 return nil, fmt.Errorf("failed converting yaml event data to JSON: %s", err) 175 } 176 return data, nil 177 default: 178 return nil, fmt.Errorf("unsupported event content type: %s", event.Context.DataContentType) 179 } 180 } 181 182 // helper method to resolve the parameter's value from the src 183 // returns value and value type (jsonType or stringType or empty string if not found). jsonType represent a block while stringType represent a single value. 184 // returns an error if the Path is invalid/not found and the default value is nil OR if the eventDependency event doesn't exist and default value is nil 185 func ResolveParamValue(src *v1alpha1.TriggerParameterSource, events map[string]*v1alpha1.Event) (*string, string, error) { 186 var err error 187 var eventPayload []byte 188 var key string 189 var tmplt string 190 var resultValue string 191 192 event, eventExists := events[src.DependencyName] 193 switch { 194 case eventExists: 195 // If no data or context selection was provided 196 if src.ContextKey == "" && src.DataKey == "" && src.DataTemplate == "" && src.ContextTemplate == "" { 197 // Return default value if exists 198 if src.Value != nil { 199 resultValue = *src.Value 200 } else { 201 // Default value doesn't exist so return the whole event payload 202 eventPayload, err = json.Marshal(&event) 203 resultValue = string(eventPayload) 204 } 205 206 if err == nil { 207 return &resultValue, stringType, nil 208 } 209 } 210 211 // Get the context part of the payload 212 if src.ContextKey != "" || src.ContextTemplate != "" { 213 key = src.ContextKey 214 tmplt = src.ContextTemplate 215 eventPayload, err = json.Marshal(&event.Context) 216 } 217 218 // Get the data part of the payload 219 if src.DataKey != "" || src.DataTemplate != "" { 220 key = src.DataKey 221 tmplt = src.DataTemplate 222 eventPayload, err = renderEventDataAsJSON(event) 223 } 224 case src.Value != nil: 225 // Use the default value set by the user in case the event is missing 226 resultValue = *src.Value 227 return &resultValue, stringType, nil 228 default: 229 // The parameter doesn't have a default value and is referencing a dependency that is 230 // missing in the received events. This is not an error and may happen with || conditions. 231 return nil, stringType, nil 232 } 233 // If the event payload parsing failed 234 if err != nil { 235 // Fall back to the default value in case it exists 236 if src.Value != nil { 237 fmt.Printf("failed to parse the event payload, using default value. err: %+v\n", err) 238 resultValue = *src.Value 239 return &resultValue, stringType, nil 240 } 241 // Otherwise, return the error 242 return nil, "", err 243 } 244 // Get the value corresponding to specified key or template within event payload 245 if eventPayload != nil { 246 if tmplt != "" { 247 resultValue, err = getValueWithTemplate(eventPayload, tmplt) 248 if err == nil { 249 return &resultValue, stringType, nil 250 } 251 fmt.Printf("failed to execute the src event template, falling back to key or value. err: %+v\n", err) 252 } 253 if key != "" { 254 tmp, typ, err := getValueByKey(eventPayload, key) 255 // For block injection support 256 resultValue = tmp 257 if err == nil { 258 return &resultValue, typ, nil 259 } 260 fmt.Printf("failed to get value by key: %+v\n", err) 261 } 262 // In case neither key nor template resolving was successful, fall back to the default value if exists 263 if src.Value != nil { 264 resultValue = *src.Value 265 return &resultValue, stringType, nil 266 } 267 } 268 269 // if we got here it means that both key and template did not match the event payload 270 // and no default value was provided, so we need to return an error 271 return nil, "", fmt.Errorf("unable to resolve '%s' parameter value. err: %+v", src.DependencyName, err) 272 } 273 274 // getValueWithTemplate will attempt to execute the provided template against 275 // the raw json bytes and then returns the result or any error 276 func getValueWithTemplate(value []byte, templString string) (string, error) { 277 res := gjson.ParseBytes(value) 278 tpl, err := template.New("param").Funcs(sprig.FuncMap()).Parse(templString) 279 if err != nil { 280 return "", err 281 } 282 var buf bytes.Buffer 283 if err := tpl.Execute(&buf, map[string]interface{}{ 284 "Input": res.Value(), 285 }); err != nil { 286 return "", err 287 } 288 out := buf.String() 289 if out == "" || out == "<no value>" { 290 return "", fmt.Errorf("template evaluated to empty string or no value: %s", templString) 291 } 292 return out, nil 293 } 294 295 // getValueByKey will return the value as raw json or a string and value's type at the provided key, 296 // Value type (jsonType or stringType or empty string). JSON represent a block while String represent a single value. 297 // or an error if it does not exist. 298 func getValueByKey(value []byte, key string) (string, string, error) { 299 res := gjson.GetBytes(value, key) 300 if res.Exists() { 301 if res.Type.String() == jsonType { 302 return res.Raw, res.Type.String(), nil 303 } 304 return res.String(), res.Type.String(), nil 305 } 306 return "", "", fmt.Errorf("key %s does not exist to in the event payload", key) 307 }