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 }