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  }