github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/tms/tmsClient.go (about)

     1  package tms
     2  
     3  import (
     4  	"bytes"
     5  	b64 "encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/url"
    11  	"os"
    12  	"strconv"
    13  	"strings"
    14  
    15  	piperHttp "github.com/SAP/jenkins-library/pkg/http"
    16  	"github.com/SAP/jenkins-library/pkg/log"
    17  	"github.com/SAP/jenkins-library/pkg/xsuaa"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  // NewCommunicationInstance returns CommunicationInstance structure with http client prepared for communication with TMS backend
    22  func NewCommunicationInstance(httpClient piperHttp.Uploader, tmsUrl, uaaUrl, clientId, clientSecret string, isVerbose bool, clientOptions piperHttp.ClientOptions) (*CommunicationInstance, error) {
    23  	logger := log.Entry().WithField("package", "SAP/jenkins-library/pkg/tms")
    24  
    25  	communicationInstance := &CommunicationInstance{
    26  		tmsUrl:       tmsUrl,
    27  		uaaUrl:       uaaUrl,
    28  		clientId:     clientId,
    29  		clientSecret: clientSecret,
    30  		httpClient:   httpClient,
    31  		logger:       logger,
    32  		isVerbose:    isVerbose,
    33  	}
    34  
    35  	token, err := communicationInstance.getOAuthToken()
    36  	if err != nil {
    37  		return communicationInstance, errors.Wrap(err, "Error fetching OAuth token")
    38  	}
    39  
    40  	clientOptions.Token = token
    41  	communicationInstance.httpClient.SetOptions(clientOptions)
    42  
    43  	return communicationInstance, nil
    44  }
    45  
    46  func (communicationInstance *CommunicationInstance) getOAuthToken() (string, error) {
    47  	if communicationInstance.isVerbose {
    48  		communicationInstance.logger.Info("OAuth token retrieval started")
    49  		communicationInstance.logger.Infof("uaaUrl: %v, clientId: %v", communicationInstance.uaaUrl, communicationInstance.clientId)
    50  	}
    51  
    52  	encodedUsernameColonPassword := b64.StdEncoding.EncodeToString([]byte(communicationInstance.clientId + ":" + communicationInstance.clientSecret))
    53  	log.RegisterSecret(encodedUsernameColonPassword)
    54  	header := http.Header{}
    55  	header.Add("Content-Type", "application/x-www-form-urlencoded")
    56  	header.Add("Authorization", "Basic "+encodedUsernameColonPassword)
    57  
    58  	urlFormData := url.Values{
    59  		"username":   {communicationInstance.clientId},
    60  		"password":   {communicationInstance.clientSecret},
    61  		"grant_type": {"password"},
    62  	}
    63  
    64  	data, err := sendRequest(communicationInstance, http.MethodPost, "/oauth/token/?grant_type=client_credentials&response_type=token", strings.NewReader(urlFormData.Encode()), header, http.StatusOK, true)
    65  	if err != nil {
    66  		return "", err
    67  	}
    68  
    69  	var token xsuaa.AuthToken
    70  	json.Unmarshal(data, &token)
    71  
    72  	if communicationInstance.isVerbose {
    73  		communicationInstance.logger.Info("OAuth Token retrieved successfully")
    74  	}
    75  	log.RegisterSecret(token.AccessToken)
    76  	return token.TokenType + " " + token.AccessToken, nil
    77  }
    78  
    79  func sendRequest(communicationInstance *CommunicationInstance, method, urlPathAndQuery string, body io.Reader, header http.Header, expectedStatusCode int, isTowardsUaa bool) ([]byte, error) {
    80  	var requestBody io.Reader
    81  	if body != nil {
    82  		closer := io.NopCloser(body)
    83  		bodyBytes, _ := io.ReadAll(closer)
    84  		requestBody = bytes.NewBuffer(bodyBytes)
    85  		defer closer.Close()
    86  	}
    87  
    88  	url := communicationInstance.tmsUrl
    89  	if isTowardsUaa {
    90  		url = communicationInstance.uaaUrl
    91  	}
    92  	url = strings.TrimSuffix(url, "/")
    93  
    94  	response, err := communicationInstance.httpClient.SendRequest(method, fmt.Sprintf("%v%v", url, urlPathAndQuery), requestBody, header, nil)
    95  
    96  	// err is not nil for HTTP status codes >= 300
    97  	if err != nil {
    98  		communicationInstance.logger.Errorf("HTTP request failed with error: %s", err)
    99  		communicationInstance.logResponseBody(response)
   100  		return nil, err
   101  	}
   102  
   103  	if response.StatusCode != expectedStatusCode {
   104  		return nil, fmt.Errorf("unexpected positive HTTP status code %v, while it was expected %v", response.StatusCode, expectedStatusCode)
   105  	}
   106  
   107  	data, _ := io.ReadAll(response.Body)
   108  	if !isTowardsUaa && communicationInstance.isVerbose {
   109  		communicationInstance.logger.Debugf("Valid response body: %v", string(data))
   110  	}
   111  	defer response.Body.Close()
   112  	return data, nil
   113  }
   114  
   115  func (communicationInstance *CommunicationInstance) logResponseBody(response *http.Response) {
   116  	if response != nil && response.Body != nil {
   117  		data, _ := io.ReadAll(response.Body)
   118  		communicationInstance.logger.Errorf("Response body: %s", data)
   119  		response.Body.Close()
   120  	}
   121  }
   122  
   123  func (communicationInstance *CommunicationInstance) GetNodes() ([]Node, error) {
   124  	if communicationInstance.isVerbose {
   125  		communicationInstance.logger.Info("Obtaining nodes started")
   126  		communicationInstance.logger.Infof("tmsUrl: %v", communicationInstance.tmsUrl)
   127  	}
   128  
   129  	header := http.Header{}
   130  	header.Add("Content-Type", "application/json")
   131  
   132  	var aNodes []Node
   133  	var data []byte
   134  	data, err := sendRequest(communicationInstance, http.MethodGet, "/v2/nodes", nil, header, http.StatusOK, false)
   135  	if err != nil {
   136  		return aNodes, err
   137  	}
   138  
   139  	var getNodesResponse nodes
   140  	json.Unmarshal(data, &getNodesResponse)
   141  	aNodes = getNodesResponse.Nodes
   142  	if communicationInstance.isVerbose {
   143  		communicationInstance.logger.Info("Nodes obtained successfully")
   144  	}
   145  	return aNodes, nil
   146  }
   147  
   148  func (communicationInstance *CommunicationInstance) GetMtaExtDescriptor(nodeId int64, mtaId, mtaVersion string) (MtaExtDescriptor, error) {
   149  	if communicationInstance.isVerbose {
   150  		communicationInstance.logger.Info("Get MTA extension descriptor started")
   151  		communicationInstance.logger.Infof("tmsUrl: %v, nodeId: %v, mtaId: %v, mtaVersion: %v", communicationInstance.tmsUrl, nodeId, mtaId, mtaVersion)
   152  	}
   153  
   154  	header := http.Header{}
   155  	header.Add("Content-Type", "application/json")
   156  
   157  	var mtaExtDescriptor MtaExtDescriptor
   158  	var data []byte
   159  	data, err := sendRequest(communicationInstance, http.MethodGet, fmt.Sprintf("/v2/nodes/%v/mtaExtDescriptors?mtaId=%v&mtaVersion=%v", nodeId, mtaId, mtaVersion), nil, header, http.StatusOK, false)
   160  	if err != nil {
   161  		return mtaExtDescriptor, err
   162  	}
   163  
   164  	var getMtaExtDescriptorsResponse mtaExtDescriptors
   165  	json.Unmarshal(data, &getMtaExtDescriptorsResponse)
   166  	if len(getMtaExtDescriptorsResponse.MtaExtDescriptors) > 0 {
   167  		mtaExtDescriptor = getMtaExtDescriptorsResponse.MtaExtDescriptors[0]
   168  	}
   169  
   170  	if communicationInstance.isVerbose {
   171  		if mtaExtDescriptor != (MtaExtDescriptor{}) {
   172  			communicationInstance.logger.Info("MTA extension descriptor obtained successfully")
   173  		} else {
   174  			communicationInstance.logger.Warn("No MTA extension descriptor found")
   175  		}
   176  	}
   177  	return mtaExtDescriptor, nil
   178  
   179  }
   180  
   181  func (communicationInstance *CommunicationInstance) UploadFileToNode(fileInfo FileInfo, nodeName, description, namedUser string) (NodeUploadResponseEntity, error) {
   182  	fileId := strconv.FormatInt(fileInfo.Id, 10)
   183  
   184  	if communicationInstance.isVerbose {
   185  		communicationInstance.logger.Info("Node upload started")
   186  		communicationInstance.logger.Infof("tmsUrl: %v, nodeName: %v, fileId: %v, description: %v, namedUser: %v", communicationInstance.tmsUrl, nodeName, fileId, description, namedUser)
   187  	}
   188  
   189  	header := http.Header{}
   190  	header.Add("Content-Type", "application/json")
   191  
   192  	var nodeUploadResponseEntity NodeUploadResponseEntity
   193  	entry := Entry{Uri: fileId}
   194  	body := NodeUploadRequestEntity{ContentType: "MTA", StorageType: "FILE", NodeName: nodeName, Description: description, NamedUser: namedUser, Entries: []Entry{entry}}
   195  	bodyBytes, errMarshaling := json.Marshal(body)
   196  	if errMarshaling != nil {
   197  		return nodeUploadResponseEntity, errors.Wrapf(errMarshaling, "unable to marshal request body %v", body)
   198  	}
   199  
   200  	data, errSendRequest := sendRequest(communicationInstance, http.MethodPost, "/v2/nodes/upload", bytes.NewReader(bodyBytes), header, http.StatusOK, false)
   201  	if errSendRequest != nil {
   202  		return nodeUploadResponseEntity, errSendRequest
   203  	}
   204  
   205  	json.Unmarshal(data, &nodeUploadResponseEntity)
   206  	communicationInstance.logger.Info("Node upload executed successfully")
   207  	communicationInstance.logger.Infof("nodeName: %v, nodeId: %v, uploadedFile: %v, createdTransportRequestDescription: %v, createdTransportRequestId: %v", nodeUploadResponseEntity.QueueEntries[0].NodeName, nodeUploadResponseEntity.QueueEntries[0].NodeId, fileInfo.Name, nodeUploadResponseEntity.TransportRequestDescription, nodeUploadResponseEntity.TransportRequestId)
   208  
   209  	return nodeUploadResponseEntity, nil
   210  
   211  }
   212  
   213  func (communicationInstance *CommunicationInstance) ExportFileToNode(fileInfo FileInfo, nodeName, description, namedUser string) (NodeUploadResponseEntity, error) {
   214  	fileId := strconv.FormatInt(fileInfo.Id, 10)
   215  
   216  	if communicationInstance.isVerbose {
   217  		communicationInstance.logger.Info("Node export started")
   218  		communicationInstance.logger.Infof("tmsUrl: %v, nodeName: %v, fileId: %v, description: %v, namedUser: %v", communicationInstance.tmsUrl, nodeName, fileId, description, namedUser)
   219  	}
   220  
   221  	header := http.Header{}
   222  	header.Add("Content-Type", "application/json")
   223  
   224  	var nodeUploadResponseEntity NodeUploadResponseEntity
   225  	entry := Entry{Uri: fileId}
   226  	body := NodeUploadRequestEntity{ContentType: "MTA", StorageType: "FILE", NodeName: nodeName, Description: description, NamedUser: namedUser, Entries: []Entry{entry}}
   227  	bodyBytes, errMarshaling := json.Marshal(body)
   228  	if errMarshaling != nil {
   229  		return nodeUploadResponseEntity, errors.Wrapf(errMarshaling, "unable to marshal request body %v", body)
   230  	}
   231  
   232  	data, errSendRequest := sendRequest(communicationInstance, http.MethodPost, "/v2/nodes/export", bytes.NewReader(bodyBytes), header, http.StatusOK, false)
   233  	if errSendRequest != nil {
   234  		return nodeUploadResponseEntity, errSendRequest
   235  	}
   236  
   237  	json.Unmarshal(data, &nodeUploadResponseEntity)
   238  	communicationInstance.logger.Info("Node export executed successfully")
   239  	communicationInstance.logger.Infof("nodeName: %v, nodeId: %v, uploadedFile: %v, createdTransportRequestDescription: %v, createdTransportRequestId: %v", nodeUploadResponseEntity.QueueEntries[0].NodeName, nodeUploadResponseEntity.QueueEntries[0].NodeId, fileInfo.Name, nodeUploadResponseEntity.TransportRequestDescription, nodeUploadResponseEntity.TransportRequestId)
   240  	return nodeUploadResponseEntity, nil
   241  
   242  }
   243  
   244  func (communicationInstance *CommunicationInstance) UpdateMtaExtDescriptor(nodeId, idOfMtaExtDescriptor int64, file, mtaVersion, description, namedUser string) (MtaExtDescriptor, error) {
   245  	if communicationInstance.isVerbose {
   246  		communicationInstance.logger.Info("Update of MTA extension descriptor started")
   247  		communicationInstance.logger.Infof("tmsUrl: %v, nodeId: %v, mtaExtDescriptorId: %v, file: %v, mtaVersion: %v, description: %v, namedUser: %v", communicationInstance.tmsUrl, nodeId, idOfMtaExtDescriptor, file, mtaVersion, description, namedUser)
   248  	}
   249  
   250  	header := http.Header{}
   251  	header.Add("tms-named-user", namedUser)
   252  
   253  	tmsUrl := strings.TrimSuffix(communicationInstance.tmsUrl, "/")
   254  	url := fmt.Sprintf("%v/v2/nodes/%v/mtaExtDescriptors/%v", tmsUrl, nodeId, idOfMtaExtDescriptor)
   255  	formFields := map[string]string{"mtaVersion": mtaVersion, "description": description}
   256  
   257  	var mtaExtDescriptor MtaExtDescriptor
   258  	fileHandle, errOpenFile := os.Open(file)
   259  	if errOpenFile != nil {
   260  		return mtaExtDescriptor, errors.Wrapf(errOpenFile, "unable to locate file %v", file)
   261  	}
   262  	defer fileHandle.Close()
   263  
   264  	uploadRequestData := piperHttp.UploadRequestData{Method: http.MethodPut, URL: url, File: file, FileFieldName: "file", FormFields: formFields, FileContent: fileHandle, Header: header, Cookies: nil}
   265  
   266  	var data []byte
   267  	data, errUpload := upload(communicationInstance, uploadRequestData, http.StatusOK)
   268  	if errUpload != nil {
   269  		return mtaExtDescriptor, errUpload
   270  	}
   271  
   272  	json.Unmarshal(data, &mtaExtDescriptor)
   273  	if communicationInstance.isVerbose {
   274  		communicationInstance.logger.Info("MTA extension descriptor updated successfully")
   275  	}
   276  	return mtaExtDescriptor, nil
   277  
   278  }
   279  
   280  func (communicationInstance *CommunicationInstance) UploadMtaExtDescriptorToNode(nodeId int64, file, mtaVersion, description, namedUser string) (MtaExtDescriptor, error) {
   281  	if communicationInstance.isVerbose {
   282  		communicationInstance.logger.Info("Upload of MTA extension descriptor started")
   283  		communicationInstance.logger.Infof("tmsUrl: %v, nodeId: %v, file: %v, mtaVersion: %v, description: %v, namedUser: %v", communicationInstance.tmsUrl, nodeId, file, mtaVersion, description, namedUser)
   284  	}
   285  
   286  	header := http.Header{}
   287  	header.Add("tms-named-user", namedUser)
   288  
   289  	tmsUrl := strings.TrimSuffix(communicationInstance.tmsUrl, "/")
   290  	url := fmt.Sprintf("%v/v2/nodes/%v/mtaExtDescriptors", tmsUrl, nodeId)
   291  	formFields := map[string]string{"mtaVersion": mtaVersion, "description": description}
   292  
   293  	var mtaExtDescriptor MtaExtDescriptor
   294  	fileHandle, errOpenFile := os.Open(file)
   295  	if errOpenFile != nil {
   296  		return mtaExtDescriptor, errors.Wrapf(errOpenFile, "unable to locate file %v", file)
   297  	}
   298  	defer fileHandle.Close()
   299  
   300  	uploadRequestData := piperHttp.UploadRequestData{Method: http.MethodPost, URL: url, File: file, FileFieldName: "file", FormFields: formFields, FileContent: fileHandle, Header: header, Cookies: nil}
   301  
   302  	var data []byte
   303  	data, errUpload := upload(communicationInstance, uploadRequestData, http.StatusCreated)
   304  	if errUpload != nil {
   305  		return mtaExtDescriptor, errUpload
   306  	}
   307  
   308  	json.Unmarshal(data, &mtaExtDescriptor)
   309  	if communicationInstance.isVerbose {
   310  		communicationInstance.logger.Info("MTA extension descriptor uploaded successfully")
   311  	}
   312  	return mtaExtDescriptor, nil
   313  
   314  }
   315  
   316  func (communicationInstance *CommunicationInstance) UploadFile(file, namedUser string) (FileInfo, error) {
   317  	if communicationInstance.isVerbose {
   318  		communicationInstance.logger.Info("Upload of file started")
   319  		communicationInstance.logger.Infof("tmsUrl: %v, file: %v, namedUser: %v", communicationInstance.tmsUrl, file, namedUser)
   320  	}
   321  
   322  	tmsUrl := strings.TrimSuffix(communicationInstance.tmsUrl, "/")
   323  	url := fmt.Sprintf("%v/v2/files/upload", tmsUrl)
   324  	formFields := map[string]string{"namedUser": namedUser}
   325  
   326  	var fileInfo FileInfo
   327  	fileHandle, errOpenFile := os.Open(file)
   328  	if errOpenFile != nil {
   329  		return fileInfo, errors.Wrapf(errOpenFile, "unable to locate file %v", file)
   330  	}
   331  	defer fileHandle.Close()
   332  
   333  	uploadRequestData := piperHttp.UploadRequestData{Method: http.MethodPost, URL: url, File: file, FileFieldName: "file", FormFields: formFields, FileContent: fileHandle, Header: http.Header{}, Cookies: nil}
   334  
   335  	var data []byte
   336  	data, errUpload := upload(communicationInstance, uploadRequestData, http.StatusCreated)
   337  	if errUpload != nil {
   338  		return fileInfo, errUpload
   339  	}
   340  
   341  	json.Unmarshal(data, &fileInfo)
   342  	if communicationInstance.isVerbose {
   343  		communicationInstance.logger.Info("File uploaded successfully")
   344  	}
   345  	return fileInfo, nil
   346  
   347  }
   348  
   349  func upload(communicationInstance *CommunicationInstance, uploadRequestData piperHttp.UploadRequestData, expectedStatusCode int) ([]byte, error) {
   350  	response, err := communicationInstance.httpClient.Upload(uploadRequestData)
   351  
   352  	// err is not nil for HTTP status codes >= 300
   353  	if err != nil {
   354  		communicationInstance.logger.Errorf("HTTP request failed with error: %s", err)
   355  		communicationInstance.logResponseBody(response)
   356  		return nil, err
   357  	}
   358  
   359  	if response.StatusCode != expectedStatusCode {
   360  		return nil, fmt.Errorf("unexpected positive HTTP status code %v, while it was expected %v", response.StatusCode, expectedStatusCode)
   361  	}
   362  
   363  	data, _ := io.ReadAll(response.Body)
   364  	if communicationInstance.isVerbose {
   365  		communicationInstance.logger.Debugf("Valid response body: %v", string(data))
   366  	}
   367  	defer response.Body.Close()
   368  	return data, nil
   369  }