github.com/redhat-appstudio/e2e-tests@v0.0.0-20230619105049-9a422b2094d7/pkg/framework/webhook.go (about)

     1  /*
     2  Some of the code is copied and refactored from GoHooks library: https://pkg.go.dev/github.com/averageflow/gohooks/v2/gohooks
     3  Original version is available on https://github.com/averageflow/gohooks/blob/v2.2.0/gohooks/GoHook.go
     4  
     5  MIT License:
     6  Copyright (c) 2013-2014 Onsi Fakhouri
     7  
     8  Permission is hereby granted, free of charge, to any person obtaining
     9  a copy of this software and associated documentation files (the
    10  "Software"), to deal in the Software without restriction, including
    11  without limitation the rights to use, copy, modify, merge, publish,
    12  distribute, sublicense, and/or sell copies of the Software, and to
    13  permit persons to whom the Software is furnished to do so, subject to
    14  the following conditions:
    15  
    16  The above copyright notice and this permission notice shall be
    17  included in all copies or substantial portions of the Software.
    18  
    19  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    20  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    21  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    22  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    23  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    24  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    25  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    26  */
    27  
    28  package framework
    29  
    30  import (
    31  	"bytes"
    32  	"crypto/hmac"
    33  	"crypto/sha256"
    34  	"crypto/tls"
    35  	"encoding/hex"
    36  	"encoding/json"
    37  	"net/http"
    38  	"os"
    39  	"time"
    40  
    41  	"gopkg.in/yaml.v2"
    42  	"k8s.io/klog/v2"
    43  
    44  	"path/filepath"
    45  )
    46  
    47  // GoWebHook represents the definition of a GoWebHook.
    48  type GoWebHook struct {
    49  	// Data to be sent in the GoWebHook
    50  	Payload GoWebHookPayload
    51  	// The encrypted SHA resulting with the used salt
    52  	ResultingSha string
    53  	// Prepared JSON marshaled data
    54  	PreparedData []byte
    55  	// Choice of signature header to use on sending a GoWebHook
    56  	SignatureHeader string
    57  	// Should validate SSL certificate
    58  	IsSecure bool
    59  	// Preferred HTTP method to send the GoWebHook
    60  	// Please choose only POST, DELETE, PATCH or PUT
    61  	// Any other value will make the send use POST as fallback
    62  	PreferredMethod string
    63  	// Additional HTTP headers to be added to the hook
    64  	AdditionalHeaders map[string]string
    65  }
    66  
    67  // GoWebHookPayload represents the data that will be sent in the GoWebHook.
    68  type GoWebHookPayload struct {
    69  	Resource string      `json:"resource"`
    70  	Data     interface{} `json:"data"`
    71  }
    72  
    73  const (
    74  	DefaultSignatureHeader = "X-GoWebHooks-Verification"
    75  )
    76  
    77  // Config struct for webhook config
    78  type Config struct {
    79  	WebhookConfig `yaml:"webhookConfig"`
    80  }
    81  
    82  // Webhook config struct
    83  type WebhookConfig struct {
    84  	SaltSecret        string `yaml:"saltSecret"`
    85  	WebhookTarget     string `yaml:"webhookTarget"`
    86  	RepositoryURL     string `yaml:"repositoryURL"`
    87  	RepositoryWebhook `yaml:"repository"`
    88  }
    89  
    90  // RepositoryWebhook config struct
    91  type RepositoryWebhook struct {
    92  	FullName   string `yaml:"fullName"`
    93  	PullNumber string `yaml:"pullNumber"`
    94  }
    95  
    96  // Webhook struct for sending
    97  type Webhook struct {
    98  	Path          string `json:"path"`
    99  	RepositoryURL string `json:"repository_url"`
   100  	Repository    `json:"repository"`
   101  }
   102  
   103  // Repository struct for sending
   104  type Repository struct {
   105  	FullName   string `json:"full_name"`
   106  	PullNumber string `json:"pull_number"`
   107  }
   108  
   109  // Create creates a webhook to be sent to another system,
   110  // with a SHA 256 signature based on its contents.
   111  func (hook *GoWebHook) Create(data interface{}, resource, secret string) {
   112  	hook.Payload.Resource = resource
   113  	hook.Payload.Data = data
   114  
   115  	preparedHookData, err := json.Marshal(hook.Payload)
   116  	if err != nil {
   117  		klog.Error(err.Error())
   118  	}
   119  
   120  	hook.PreparedData = preparedHookData
   121  
   122  	h := hmac.New(sha256.New, []byte(secret))
   123  
   124  	_, err = h.Write(preparedHookData)
   125  	if err != nil {
   126  		klog.Error(err.Error())
   127  	}
   128  
   129  	// Get result and encode as hexadecimal string
   130  	hook.ResultingSha = hex.EncodeToString(h.Sum(nil))
   131  }
   132  
   133  // Send sends a GoWebHook to the specified URL, as a UTF-8 JSON payload.
   134  func (hook *GoWebHook) Send(receiverURL string) (*http.Response, error) {
   135  	if hook.SignatureHeader == "" {
   136  		// Use the DefaultSignatureHeader as default if no custom header is specified
   137  		hook.SignatureHeader = DefaultSignatureHeader
   138  	}
   139  
   140  	if !hook.IsSecure {
   141  		// By default do not verify SSL certificate validity
   142  		http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
   143  			InsecureSkipVerify: true, //nolint:gosec
   144  		}
   145  	}
   146  
   147  	switch hook.PreferredMethod {
   148  	case http.MethodPost, http.MethodPatch, http.MethodPut, http.MethodDelete:
   149  		// Valid Methods, do nothing
   150  	default:
   151  		// By default send GoWebHook using a POST method
   152  		hook.PreferredMethod = http.MethodPost
   153  	}
   154  
   155  	client := &http.Client{Timeout: 30 * time.Second}
   156  
   157  	req, err := http.NewRequest(
   158  		hook.PreferredMethod,
   159  		receiverURL,
   160  		bytes.NewBuffer(hook.PreparedData),
   161  	)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	req.Header.Add("Content-Type", "application/json")
   167  	req.Header.Add("Charset", "utf-8")
   168  	req.Header.Add(DefaultSignatureHeader, hook.ResultingSha)
   169  
   170  	// Add user's additional headers
   171  	for i := range hook.AdditionalHeaders {
   172  		req.Header.Add(i, hook.AdditionalHeaders[i])
   173  	}
   174  
   175  	req.Close = true
   176  
   177  	resp, err := client.Do(req)
   178  
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	return resp, nil
   184  }
   185  
   186  // Send webhook
   187  func SendWebhook(webhookConfig string) {
   188  	cfg, err := LoadConfig(webhookConfig)
   189  	if err != nil {
   190  		klog.Fatal(err)
   191  	}
   192  	path, err := os.Executable()
   193  	if err != nil {
   194  		klog.Info(err)
   195  	}
   196  
   197  	//Create webhook
   198  	hook := &GoWebHook{}
   199  	w := Webhook{Path: path}
   200  	w.RepositoryURL = cfg.WebhookConfig.RepositoryURL
   201  	w.Repository.FullName = cfg.WebhookConfig.RepositoryWebhook.FullName
   202  	w.Repository.PullNumber = cfg.WebhookConfig.RepositoryWebhook.PullNumber
   203  	saltSecret := cfg.WebhookConfig.SaltSecret
   204  	hook.Create(w, path, saltSecret)
   205  
   206  	//Send webhook
   207  	resp, err := hook.Send(cfg.WebhookConfig.WebhookTarget)
   208  	if err != nil {
   209  		klog.Fatal("Error sending webhook: ", err)
   210  	}
   211  	klog.Info("Webhook response: ", resp)
   212  }
   213  
   214  // LoadConfig returns a decoded Config struct
   215  func LoadConfig(configPath string) (*Config, error) {
   216  	// Create config structure
   217  	config := &Config{}
   218  
   219  	// Open config file
   220  	file, err := os.Open(filepath.Clean(configPath))
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	// Init new YAML decode
   226  	d := yaml.NewDecoder(file)
   227  
   228  	// Start YAML decoding from file
   229  	if err := d.Decode(&config); err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	if err := file.Close(); err != nil {
   234  		klog.Fatal("Error closing file: %s\n", err)
   235  	}
   236  
   237  	return config, nil
   238  }