github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/abap/build/connector.go (about) 1 package build 2 3 import ( 4 "bytes" 5 "io" 6 "net/http" 7 "net/http/cookiejar" 8 "net/url" 9 "strconv" 10 "time" 11 12 "github.com/SAP/jenkins-library/pkg/abaputils" 13 piperhttp "github.com/SAP/jenkins-library/pkg/http" 14 "github.com/SAP/jenkins-library/pkg/log" 15 "github.com/pkg/errors" 16 ) 17 18 // Connector : Connector Utility Wrapping http client 19 type Connector struct { 20 Client piperhttp.Sender 21 DownloadClient piperhttp.Downloader 22 Header map[string][]string 23 Baseurl string 24 Parameters url.Values 25 MaxRuntime time.Duration // just as handover parameter for polling functions 26 PollingInterval time.Duration // just as handover parameter for polling functions 27 } 28 29 // ConnectorConfiguration : Handover Structure for Connector Creation 30 type ConnectorConfiguration struct { 31 CfAPIEndpoint string 32 CfOrg string 33 CfSpace string 34 CfServiceInstance string 35 CfServiceKeyName string 36 Host string 37 Username string 38 Password string 39 AddonDescriptor string 40 MaxRuntimeInMinutes int 41 CertificateNames []string 42 Parameters url.Values 43 } 44 45 // HTTPSendLoader : combine both interfaces [sender, downloader] 46 type HTTPSendLoader interface { 47 piperhttp.Sender 48 piperhttp.Downloader 49 } 50 51 // ******** technical communication calls ******** 52 53 // GetToken : Get the X-CRSF Token from ABAP Backend for later post 54 func (conn *Connector) GetToken(appendum string) error { 55 url := conn.createUrl(appendum) 56 conn.Header["X-CSRF-Token"] = []string{"Fetch"} 57 response, err := conn.Client.SendRequest("HEAD", url, nil, conn.Header, nil) 58 if err != nil { 59 if response == nil { 60 return errors.Wrap(err, "Fetching X-CSRF-Token failed") 61 } 62 defer response.Body.Close() 63 errorbody, _ := io.ReadAll(response.Body) 64 return errors.Wrapf(err, "Fetching X-CSRF-Token failed: %v", string(errorbody)) 65 66 } 67 defer response.Body.Close() 68 token := response.Header.Get("X-CSRF-Token") 69 conn.Header["X-CSRF-Token"] = []string{token} 70 return nil 71 } 72 73 // Get : http get request 74 func (conn Connector) Get(appendum string) ([]byte, error) { 75 url := conn.createUrl(appendum) 76 response, err := conn.Client.SendRequest("GET", url, nil, conn.Header, nil) 77 if err != nil { 78 if response == nil || response.Body == nil { 79 return nil, errors.Wrap(err, "Get failed") 80 } 81 defer response.Body.Close() 82 errorbody, _ := io.ReadAll(response.Body) 83 return errorbody, errors.Wrapf(err, "Get failed: %v", string(errorbody)) 84 85 } 86 defer response.Body.Close() 87 body, err := io.ReadAll(response.Body) 88 return body, err 89 } 90 91 // Post : http post request 92 func (conn Connector) Post(appendum string, importBody string) ([]byte, error) { 93 url := conn.createUrl(appendum) 94 var response *http.Response 95 var err error 96 if importBody == "" { 97 response, err = conn.Client.SendRequest("POST", url, nil, conn.Header, nil) 98 } else { 99 response, err = conn.Client.SendRequest("POST", url, bytes.NewBuffer([]byte(importBody)), conn.Header, nil) 100 } 101 if err != nil { 102 if response == nil { 103 return nil, errors.Wrap(err, "Post failed") 104 } 105 defer response.Body.Close() 106 errorbody, _ := io.ReadAll(response.Body) 107 return errorbody, errors.Wrapf(err, "Post failed: %v", string(errorbody)) 108 109 } 110 defer response.Body.Close() 111 body, err := io.ReadAll(response.Body) 112 return body, err 113 } 114 115 // Download : download a file via http 116 func (conn Connector) Download(appendum string, downloadPath string) error { 117 url := conn.createUrl(appendum) 118 err := conn.DownloadClient.DownloadFile(url, downloadPath, nil, nil) 119 return err 120 } 121 122 // create url 123 func (conn Connector) createUrl(appendum string) string { 124 myUrl := conn.Baseurl + appendum 125 if len(conn.Parameters) == 0 { 126 return myUrl 127 } 128 myUrl = myUrl + "?" + conn.Parameters.Encode() 129 return myUrl 130 } 131 132 // InitAAKaaS : initialize Connector for communication with AAKaaS backend 133 func (conn *Connector) InitAAKaaS(aAKaaSEndpoint string, username string, password string, inputclient piperhttp.Sender) error { 134 conn.Client = inputclient 135 conn.Header = make(map[string][]string) 136 conn.Header["Accept"] = []string{"application/json"} 137 conn.Header["Content-Type"] = []string{"application/json"} 138 conn.Header["User-Agent"] = []string{"Piper-abapAddonAssemblyKit/1.0"} 139 140 cookieJar, _ := cookiejar.New(nil) 141 conn.Client.SetOptions(piperhttp.ClientOptions{ 142 Username: username, 143 Password: password, 144 CookieJar: cookieJar, 145 }) 146 conn.Baseurl = aAKaaSEndpoint 147 148 if username == "" || password == "" { 149 return errors.New("username/password for AAKaaS must not be initial") //leads to redirect to login page which causes HTTP200 instead of HTTP401 and thus side effects 150 } else { 151 return nil 152 } 153 } 154 155 // InitBuildFramework : initialize Connector for communication with ABAP SCP instance 156 func (conn *Connector) InitBuildFramework(config ConnectorConfiguration, com abaputils.Communication, inputclient HTTPSendLoader) error { 157 conn.Client = inputclient 158 conn.Header = make(map[string][]string) 159 conn.Header["Accept"] = []string{"application/json"} 160 conn.Header["Content-Type"] = []string{"application/json"} 161 162 conn.DownloadClient = inputclient 163 conn.DownloadClient.SetOptions(piperhttp.ClientOptions{TransportTimeout: 20 * time.Second}) 164 // Mapping for options 165 subOptions := abaputils.AbapEnvironmentOptions{} 166 subOptions.CfAPIEndpoint = config.CfAPIEndpoint 167 subOptions.CfServiceInstance = config.CfServiceInstance 168 subOptions.CfServiceKeyName = config.CfServiceKeyName 169 subOptions.CfOrg = config.CfOrg 170 subOptions.CfSpace = config.CfSpace 171 subOptions.Host = config.Host 172 subOptions.Password = config.Password 173 subOptions.Username = config.Username 174 175 // Determine the host, user and password, either via the input parameters or via a cloud foundry service key 176 connectionDetails, err := com.GetAbapCommunicationArrangementInfo(subOptions, "/sap/opu/odata/BUILD/CORE_SRV") 177 if err != nil { 178 return errors.Wrap(err, "Parameters for the ABAP Connection not available") 179 } 180 181 conn.DownloadClient.SetOptions(piperhttp.ClientOptions{ 182 Username: connectionDetails.User, 183 Password: connectionDetails.Password, 184 }) 185 cookieJar, _ := cookiejar.New(nil) 186 conn.Client.SetOptions(piperhttp.ClientOptions{ 187 Username: connectionDetails.User, 188 Password: connectionDetails.Password, 189 CookieJar: cookieJar, 190 TrustedCerts: config.CertificateNames, 191 }) 192 conn.Baseurl = connectionDetails.URL 193 conn.Parameters = config.Parameters 194 195 return nil 196 } 197 198 // UploadSarFile : upload *.sar file 199 func (conn Connector) UploadSarFile(appendum string, sarFile []byte) error { 200 url := conn.createUrl(appendum) 201 response, err := conn.Client.SendRequest("PUT", url, bytes.NewBuffer(sarFile), conn.Header, nil) 202 if err != nil { 203 defer response.Body.Close() 204 errorbody, _ := io.ReadAll(response.Body) 205 return errors.Wrapf(err, "Upload of SAR file failed: %v", string(errorbody)) 206 } 207 defer response.Body.Close() 208 return nil 209 } 210 211 // UploadSarFileInChunks : upload *.sar file in chunks 212 func (conn Connector) UploadSarFileInChunks(appendum string, fileName string, sarFile []byte) error { 213 //Maybe Next Refactoring step to read the file in chunks, too? 214 //In case it turns out to be not reliable add a retry mechanism 215 216 url := conn.createUrl(appendum) 217 218 header := make(map[string][]string) 219 header["Content-Disposition"] = []string{"form-data; name=\"file\"; filename=\"" + fileName + "\""} 220 221 //chunkSize := 10000 // 10KB for testing 222 //chunkSize := 1000000 //1MB for Testing, 223 chunkSize := 10000000 //10MB 224 log.Entry().Infof("Upload in chunks of %d bytes", chunkSize) 225 226 sarFileBuffer := bytes.NewBuffer(sarFile) 227 fileSize := sarFileBuffer.Len() 228 229 for sarFileBuffer.Len() > 0 { 230 startOffset := fileSize - sarFileBuffer.Len() 231 nextChunk := bytes.NewBuffer(sarFileBuffer.Next(chunkSize)) 232 endOffset := fileSize - sarFileBuffer.Len() 233 header["Content-Range"] = []string{"bytes " + strconv.Itoa(startOffset) + " - " + strconv.Itoa(endOffset) + " / " + strconv.Itoa(fileSize)} 234 log.Entry().Info(header["Content-Range"]) 235 236 response, err := conn.Client.SendRequest("POST", url, nextChunk, header, nil) 237 if err != nil { 238 if response != nil && response.Body != nil { 239 errorbody, _ := io.ReadAll(response.Body) 240 response.Body.Close() 241 return errors.Wrapf(err, "Upload of SAR file failed: %v", string(errorbody)) 242 } else { 243 return err 244 } 245 } 246 247 response.Body.Close() 248 } 249 return nil 250 }