2020-09-17 11:01:19 +02:00
package build
import (
"bytes"
"io/ioutil"
"net/http"
"net/http/cookiejar"
2022-07-07 08:44:51 +02:00
"net/url"
2020-11-26 14:45:51 +02:00
"strconv"
2020-09-30 16:40:36 +02:00
"time"
2020-09-17 11:01:19 +02:00
2020-09-30 16:40:36 +02:00
"github.com/SAP/jenkins-library/pkg/abaputils"
2020-09-17 11:01:19 +02:00
piperhttp "github.com/SAP/jenkins-library/pkg/http"
2020-11-26 14:45:51 +02:00
"github.com/SAP/jenkins-library/pkg/log"
2020-09-17 11:01:19 +02:00
"github.com/pkg/errors"
)
// Connector : Connector Utility Wrapping http client
type Connector struct {
2021-11-12 15:33:18 +02:00
Client piperhttp . Sender
DownloadClient piperhttp . Downloader
Header map [ string ] [ ] string
Baseurl string
2022-07-07 08:44:51 +02:00
Parameters url . Values
2021-11-12 15:33:18 +02:00
MaxRuntime time . Duration // just as handover parameter for polling functions
PollingInterval time . Duration // just as handover parameter for polling functions
2020-09-17 11:01:19 +02:00
}
2020-09-30 16:40:36 +02:00
// ConnectorConfiguration : Handover Structure for Connector Creation
type ConnectorConfiguration struct {
CfAPIEndpoint string
CfOrg string
CfSpace string
CfServiceInstance string
CfServiceKeyName string
Host string
Username string
Password string
AddonDescriptor string
MaxRuntimeInMinutes int
2021-12-06 15:43:37 +02:00
CertificateNames [ ] string
2022-07-07 08:44:51 +02:00
Parameters url . Values
2020-09-30 16:40:36 +02:00
}
2021-03-01 14:51:44 +02:00
// HTTPSendLoader : combine both interfaces [sender, downloader]
type HTTPSendLoader interface {
piperhttp . Sender
piperhttp . Downloader
}
2020-09-17 11:01:19 +02:00
// ******** technical communication calls ********
// GetToken : Get the X-CRSF Token from ABAP Backend for later post
func ( conn * Connector ) GetToken ( appendum string ) error {
2022-07-07 08:44:51 +02:00
url := conn . createUrl ( appendum )
2020-09-17 11:01:19 +02:00
conn . Header [ "X-CSRF-Token" ] = [ ] string { "Fetch" }
response , err := conn . Client . SendRequest ( "HEAD" , url , nil , conn . Header , nil )
if err != nil {
if response == nil {
return errors . Wrap ( err , "Fetching X-CSRF-Token failed" )
}
defer response . Body . Close ( )
errorbody , _ := ioutil . ReadAll ( response . Body )
return errors . Wrapf ( err , "Fetching X-CSRF-Token failed: %v" , string ( errorbody ) )
}
defer response . Body . Close ( )
token := response . Header . Get ( "X-CSRF-Token" )
conn . Header [ "X-CSRF-Token" ] = [ ] string { token }
return nil
}
// Get : http get request
func ( conn Connector ) Get ( appendum string ) ( [ ] byte , error ) {
2022-07-07 08:44:51 +02:00
url := conn . createUrl ( appendum )
2020-09-17 11:01:19 +02:00
response , err := conn . Client . SendRequest ( "GET" , url , nil , conn . Header , nil )
if err != nil {
2020-09-30 10:30:53 +02:00
if response == nil || response . Body == nil {
2020-09-17 11:01:19 +02:00
return nil , errors . Wrap ( err , "Get failed" )
}
defer response . Body . Close ( )
errorbody , _ := ioutil . ReadAll ( response . Body )
return errorbody , errors . Wrapf ( err , "Get failed: %v" , string ( errorbody ) )
}
defer response . Body . Close ( )
body , err := ioutil . ReadAll ( response . Body )
return body , err
}
// Post : http post request
func ( conn Connector ) Post ( appendum string , importBody string ) ( [ ] byte , error ) {
2022-07-07 08:44:51 +02:00
url := conn . createUrl ( appendum )
2020-09-17 11:01:19 +02:00
var response * http . Response
var err error
if importBody == "" {
response , err = conn . Client . SendRequest ( "POST" , url , nil , conn . Header , nil )
} else {
response , err = conn . Client . SendRequest ( "POST" , url , bytes . NewBuffer ( [ ] byte ( importBody ) ) , conn . Header , nil )
}
if err != nil {
if response == nil {
return nil , errors . Wrap ( err , "Post failed" )
}
defer response . Body . Close ( )
errorbody , _ := ioutil . ReadAll ( response . Body )
return errorbody , errors . Wrapf ( err , "Post failed: %v" , string ( errorbody ) )
}
defer response . Body . Close ( )
body , err := ioutil . ReadAll ( response . Body )
return body , err
}
// Download : download a file via http
func ( conn Connector ) Download ( appendum string , downloadPath string ) error {
2022-07-07 08:44:51 +02:00
url := conn . createUrl ( appendum )
2020-09-17 11:01:19 +02:00
err := conn . DownloadClient . DownloadFile ( url , downloadPath , nil , nil )
return err
}
2022-07-07 08:44:51 +02:00
// create url
func ( conn Connector ) createUrl ( appendum string ) string {
myUrl := conn . Baseurl + appendum
if len ( conn . Parameters ) == 0 {
return myUrl
}
myUrl = myUrl + "?" + conn . Parameters . Encode ( )
return myUrl
}
2020-09-30 16:40:36 +02:00
// InitAAKaaS : initialize Connector for communication with AAKaaS backend
2021-11-02 12:00:01 +02:00
func ( conn * Connector ) InitAAKaaS ( aAKaaSEndpoint string , username string , password string , inputclient piperhttp . Sender ) error {
2020-09-17 11:01:19 +02:00
conn . Client = inputclient
conn . Header = make ( map [ string ] [ ] string )
conn . Header [ "Accept" ] = [ ] string { "application/json" }
conn . Header [ "Content-Type" ] = [ ] string { "application/json" }
2021-04-29 13:30:25 +02:00
conn . Header [ "User-Agent" ] = [ ] string { "Piper-abapAddonAssemblyKit/1.0" }
2020-09-17 11:01:19 +02:00
cookieJar , _ := cookiejar . New ( nil )
conn . Client . SetOptions ( piperhttp . ClientOptions {
Username : username ,
Password : password ,
CookieJar : cookieJar ,
} )
conn . Baseurl = aAKaaSEndpoint
2021-11-02 12:00:01 +02:00
if username == "" || password == "" {
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
} else {
return nil
}
2020-09-17 11:01:19 +02:00
}
2020-09-18 14:07:42 +02:00
2020-09-30 16:40:36 +02:00
// InitBuildFramework : initialize Connector for communication with ABAP SCP instance
2021-03-01 14:51:44 +02:00
func ( conn * Connector ) InitBuildFramework ( config ConnectorConfiguration , com abaputils . Communication , inputclient HTTPSendLoader ) error {
2020-09-30 16:40:36 +02:00
conn . Client = inputclient
conn . Header = make ( map [ string ] [ ] string )
conn . Header [ "Accept" ] = [ ] string { "application/json" }
conn . Header [ "Content-Type" ] = [ ] string { "application/json" }
2021-03-01 14:51:44 +02:00
conn . DownloadClient = inputclient
2020-09-30 16:40:36 +02:00
conn . DownloadClient . SetOptions ( piperhttp . ClientOptions { TransportTimeout : 20 * time . Second } )
// Mapping for options
subOptions := abaputils . AbapEnvironmentOptions { }
subOptions . CfAPIEndpoint = config . CfAPIEndpoint
subOptions . CfServiceInstance = config . CfServiceInstance
subOptions . CfServiceKeyName = config . CfServiceKeyName
subOptions . CfOrg = config . CfOrg
subOptions . CfSpace = config . CfSpace
subOptions . Host = config . Host
subOptions . Password = config . Password
subOptions . Username = config . Username
// Determine the host, user and password, either via the input parameters or via a cloud foundry service key
connectionDetails , err := com . GetAbapCommunicationArrangementInfo ( subOptions , "/sap/opu/odata/BUILD/CORE_SRV" )
if err != nil {
return errors . Wrap ( err , "Parameters for the ABAP Connection not available" )
}
conn . DownloadClient . SetOptions ( piperhttp . ClientOptions {
Username : connectionDetails . User ,
Password : connectionDetails . Password ,
} )
cookieJar , _ := cookiejar . New ( nil )
conn . Client . SetOptions ( piperhttp . ClientOptions {
2021-12-06 15:43:37 +02:00
Username : connectionDetails . User ,
Password : connectionDetails . Password ,
CookieJar : cookieJar ,
TrustedCerts : config . CertificateNames ,
2020-09-30 16:40:36 +02:00
} )
conn . Baseurl = connectionDetails . URL
2022-07-07 08:44:51 +02:00
conn . Parameters = config . Parameters
2020-09-30 16:40:36 +02:00
return nil
}
2020-09-18 14:07:42 +02:00
// UploadSarFile : upload *.sar file
func ( conn Connector ) UploadSarFile ( appendum string , sarFile [ ] byte ) error {
2022-07-07 08:44:51 +02:00
url := conn . createUrl ( appendum )
2020-09-18 14:07:42 +02:00
response , err := conn . Client . SendRequest ( "PUT" , url , bytes . NewBuffer ( sarFile ) , conn . Header , nil )
if err != nil {
defer response . Body . Close ( )
errorbody , _ := ioutil . ReadAll ( response . Body )
return errors . Wrapf ( err , "Upload of SAR file failed: %v" , string ( errorbody ) )
}
defer response . Body . Close ( )
return nil
}
2020-11-26 14:45:51 +02:00
// UploadSarFileInChunks : upload *.sar file in chunks
func ( conn Connector ) UploadSarFileInChunks ( appendum string , fileName string , sarFile [ ] byte ) error {
//Maybe Next Refactoring step to read the file in chunks, too?
//In case it turns out to be not reliable add a retry mechanism
2022-07-07 08:44:51 +02:00
url := conn . createUrl ( appendum )
2020-11-26 14:45:51 +02:00
header := make ( map [ string ] [ ] string )
header [ "Content-Disposition" ] = [ ] string { "form-data; name=\"file\"; filename=\"" + fileName + "\"" }
//chunkSize := 10000 // 10KB for testing
//chunkSize := 1000000 //1MB for Testing,
chunkSize := 10000000 //10MB
log . Entry ( ) . Infof ( "Upload in chunks of %d bytes" , chunkSize )
sarFileBuffer := bytes . NewBuffer ( sarFile )
fileSize := sarFileBuffer . Len ( )
for sarFileBuffer . Len ( ) > 0 {
startOffset := fileSize - sarFileBuffer . Len ( )
nextChunk := bytes . NewBuffer ( sarFileBuffer . Next ( chunkSize ) )
endOffset := fileSize - sarFileBuffer . Len ( )
header [ "Content-Range" ] = [ ] string { "bytes " + strconv . Itoa ( startOffset ) + " - " + strconv . Itoa ( endOffset ) + " / " + strconv . Itoa ( fileSize ) }
log . Entry ( ) . Info ( header [ "Content-Range" ] )
response , err := conn . Client . SendRequest ( "POST" , url , nextChunk , header , nil )
if err != nil {
2021-11-02 12:00:01 +02:00
if response != nil && response . Body != nil {
errorbody , _ := ioutil . ReadAll ( response . Body )
response . Body . Close ( )
return errors . Wrapf ( err , "Upload of SAR file failed: %v" , string ( errorbody ) )
} else {
return err
}
2020-11-26 14:45:51 +02:00
}
response . Body . Close ( )
}
return nil
}