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 }