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  }