github.com/argoproj/argo-events@v1.9.1/sensors/triggers/http/http.go (about) 1 /* 2 Copyright 2020 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 package http 17 18 import ( 19 "bytes" 20 "context" 21 "encoding/json" 22 "fmt" 23 "net/http" 24 "time" 25 26 "go.uber.org/zap" 27 28 "github.com/argoproj/argo-events/common" 29 "github.com/argoproj/argo-events/common/logging" 30 apicommon "github.com/argoproj/argo-events/pkg/apis/common" 31 "github.com/argoproj/argo-events/pkg/apis/sensor/v1alpha1" 32 "github.com/argoproj/argo-events/sensors/policy" 33 "github.com/argoproj/argo-events/sensors/triggers" 34 ) 35 36 // HTTPTrigger describes the trigger to invoke HTTP request 37 type HTTPTrigger struct { 38 // Client is http client. 39 Client *http.Client 40 // Sensor object 41 Sensor *v1alpha1.Sensor 42 // Trigger reference 43 Trigger *v1alpha1.Trigger 44 // Logger to log stuff 45 Logger *zap.SugaredLogger 46 } 47 48 // NewHTTPTrigger returns a new HTTP trigger 49 func NewHTTPTrigger(httpClients common.StringKeyedMap[*http.Client], sensor *v1alpha1.Sensor, trigger *v1alpha1.Trigger, logger *zap.SugaredLogger) (*HTTPTrigger, error) { 50 httptrigger := trigger.Template.HTTP 51 52 client, ok := httpClients.Load(trigger.Template.Name) 53 if !ok { 54 client = &http.Client{} 55 56 if httptrigger.TLS != nil { 57 tlsConfig, err := common.GetTLSConfig(httptrigger.TLS) 58 if err != nil { 59 return nil, fmt.Errorf("failed to get the tls configuration, %w", err) 60 } 61 client.Transport = &http.Transport{ 62 TLSClientConfig: tlsConfig, 63 } 64 } 65 66 timeout := time.Second * 60 67 if httptrigger.Timeout > 0 { 68 timeout = time.Duration(httptrigger.Timeout) * time.Second 69 } 70 client.Timeout = timeout 71 72 httpClients.Store(trigger.Template.Name, client) 73 } 74 75 return &HTTPTrigger{ 76 Client: client, 77 Sensor: sensor, 78 Trigger: trigger, 79 Logger: logger.With(logging.LabelTriggerType, apicommon.HTTPTrigger), 80 }, nil 81 } 82 83 // GetTriggerType returns the type of the trigger 84 func (t *HTTPTrigger) GetTriggerType() apicommon.TriggerType { 85 return apicommon.HTTPTrigger 86 } 87 88 // FetchResource fetches the trigger. As the HTTP trigger simply executes a http request, there 89 // is no need to fetch any resource from external source 90 func (t *HTTPTrigger) FetchResource(ctx context.Context) (interface{}, error) { 91 if t.Trigger.Template.HTTP.Method == "" { 92 t.Trigger.Template.HTTP.Method = http.MethodPost 93 } 94 return t.Trigger.Template.HTTP, nil 95 } 96 97 // ApplyResourceParameters applies parameters to the trigger resource 98 func (t *HTTPTrigger) ApplyResourceParameters(events map[string]*v1alpha1.Event, resource interface{}) (interface{}, error) { 99 fetchedResource, ok := resource.(*v1alpha1.HTTPTrigger) 100 if !ok { 101 return nil, fmt.Errorf("failed to interpret the fetched trigger resource") 102 } 103 104 resourceBytes, err := json.Marshal(fetchedResource) 105 if err != nil { 106 return nil, fmt.Errorf("failed to marshal the http trigger resource, %w", err) 107 } 108 parameters := fetchedResource.Parameters 109 if parameters != nil { 110 updatedResourceBytes, err := triggers.ApplyParams(resourceBytes, parameters, events) 111 if err != nil { 112 return nil, err 113 } 114 var ht *v1alpha1.HTTPTrigger 115 if err := json.Unmarshal(updatedResourceBytes, &ht); err != nil { 116 return nil, fmt.Errorf("failed to unmarshal the updated http trigger resource after applying resource parameters, %w", err) 117 } 118 return ht, nil 119 } 120 return resource, nil 121 } 122 123 // Execute executes the trigger 124 func (t *HTTPTrigger) Execute(ctx context.Context, events map[string]*v1alpha1.Event, resource interface{}) (interface{}, error) { 125 var payload []byte 126 var err error 127 128 trigger, ok := resource.(*v1alpha1.HTTPTrigger) 129 if !ok { 130 return nil, fmt.Errorf("failed to interpret the trigger resource") 131 } 132 133 if (trigger.Method == http.MethodPost || trigger.Method == http.MethodPatch || trigger.Method == http.MethodPut) && trigger.Payload == nil { 134 t.Logger.Warnw("payload parameters are not specified. request payload will be an empty string", zap.Any("url", trigger.URL)) 135 } 136 137 if trigger.Payload != nil { 138 payload, err = triggers.ConstructPayload(events, trigger.Payload) 139 if err != nil { 140 return nil, err 141 } 142 } 143 144 request, err := http.NewRequest(trigger.Method, trigger.URL, bytes.NewReader(payload)) 145 if err != nil { 146 return nil, fmt.Errorf("failed to construct request for %s, %w", trigger.URL, err) 147 } 148 149 if trigger.Headers != nil { 150 for name, value := range trigger.Headers { 151 request.Header[name] = []string{value} 152 } 153 } 154 155 if trigger.SecureHeaders != nil { 156 for _, secure := range trigger.SecureHeaders { 157 var value string 158 var err error 159 if secure.ValueFrom.SecretKeyRef != nil { 160 value, err = common.GetSecretFromVolume(secure.ValueFrom.SecretKeyRef) 161 } else { 162 value, err = common.GetConfigMapFromVolume(secure.ValueFrom.ConfigMapKeyRef) 163 } 164 if err != nil { 165 return nil, fmt.Errorf("failed to retrieve the value for secureHeader, %w", err) 166 } 167 request.Header[secure.Name] = []string{value} 168 } 169 } 170 171 basicAuth := trigger.BasicAuth 172 173 if basicAuth != nil { 174 username := "" 175 password := "" 176 177 if basicAuth.Username != nil { 178 username, err = common.GetSecretFromVolume(basicAuth.Username) 179 if err != nil { 180 return nil, fmt.Errorf("failed to retrieve the username, %w", err) 181 } 182 } 183 184 if basicAuth.Password != nil { 185 password, err = common.GetSecretFromVolume(basicAuth.Password) 186 if !ok { 187 return nil, fmt.Errorf("failed to retrieve the password, %w", err) 188 } 189 } 190 191 request.SetBasicAuth(username, password) 192 } 193 194 t.Logger.Infow("Making a http request...", zap.Any("url", trigger.URL)) 195 196 return t.Client.Do(request) 197 } 198 199 // ApplyPolicy applies policy on the trigger 200 func (t *HTTPTrigger) ApplyPolicy(ctx context.Context, resource interface{}) error { 201 if t.Trigger.Policy == nil || t.Trigger.Policy.Status == nil || t.Trigger.Policy.Status.Allow == nil { 202 return nil 203 } 204 response, ok := resource.(*http.Response) 205 if !ok { 206 return fmt.Errorf("failed to interpret the trigger execution response") 207 } 208 209 p := policy.NewStatusPolicy(response.StatusCode, t.Trigger.Policy.Status.GetAllow()) 210 211 return p.ApplyPolicy(ctx) 212 }