github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/gctsDeploy.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "net/http/cookiejar" 10 "net/url" 11 "strings" 12 13 "github.com/Jeffail/gabs/v2" 14 "github.com/SAP/jenkins-library/pkg/command" 15 piperhttp "github.com/SAP/jenkins-library/pkg/http" 16 "github.com/SAP/jenkins-library/pkg/log" 17 "github.com/SAP/jenkins-library/pkg/telemetry" 18 "github.com/pkg/errors" 19 ) 20 21 const repoStateExists = "RepoExists" 22 const repoStateNew = "RepoNew" 23 24 func gctsDeploy(config gctsDeployOptions, telemetryData *telemetry.CustomData) { 25 // for command execution use Command 26 c := command.Command{} 27 // reroute command output to logging framework 28 c.Stdout(log.Writer()) 29 c.Stderr(log.Writer()) 30 31 // for http calls import piperhttp "github.com/SAP/jenkins-library/pkg/http" 32 // and use a &piperhttp.Client{} in a custom system 33 // Example: step checkmarxExecuteScan.go 34 httpClient := &piperhttp.Client{} 35 36 // error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end 37 err := gctsDeployRepository(&config, telemetryData, &c, httpClient) 38 if err != nil { 39 log.Entry().WithError(err).Fatal("step execution failed") 40 } 41 } 42 43 func gctsDeployRepository(config *gctsDeployOptions, telemetryData *telemetry.CustomData, command command.ExecRunner, httpClient piperhttp.Sender) error { 44 maxRetries := -1 45 cookieJar, cookieErr := cookiejar.New(nil) 46 repoState := repoStateExists 47 branchRollbackRequired := false 48 if cookieErr != nil { 49 return errors.Wrap(cookieErr, "creating a cookie jar failed") 50 } 51 clientOptions := piperhttp.ClientOptions{ 52 CookieJar: cookieJar, 53 Username: config.Username, 54 Password: config.Password, 55 MaxRetries: maxRetries, 56 TransportSkipVerification: config.SkipSSLVerification, 57 } 58 httpClient.SetOptions(clientOptions) 59 log.Entry().Infof("Start of gCTS Deploy Step with Configuration Values: %v", config) 60 configurationMetadata, getConfigMetadataErr := getConfigurationMetadata(config, httpClient) 61 62 if getConfigMetadataErr != nil { 63 log.Entry().WithError(getConfigMetadataErr).Error("step execution failed at configuration metadata retrieval. Please Check if system is up!.") 64 return getConfigMetadataErr 65 } 66 67 createRepoOptions := gctsCreateRepositoryOptions{ 68 Username: config.Username, 69 Password: config.Password, 70 Repository: config.Repository, 71 Host: config.Host, 72 Client: config.Client, 73 RemoteRepositoryURL: config.RemoteRepositoryURL, 74 Role: config.Role, 75 VSID: config.VSID, 76 Type: config.Type, 77 QueryParameters: config.QueryParameters, 78 SkipSSLVerification: config.SkipSSLVerification, 79 } 80 log.Entry().Infof("gCTS Deploy : Checking if repository %v already exists", config.Repository) 81 repoMetadataInitState, getRepositoryErr := getRepository(config, httpClient) 82 currentBranch := repoMetadataInitState.Result.Branch 83 // If Repository does not exist in the system then Create and Clone Repository 84 if getRepositoryErr != nil { 85 // If scope is set for a new repository then creation/cloning of the repository cannot be done 86 if config.Scope != "" { 87 log.Entry().Error("Error during deploy : deploy scope cannot be provided while deploying a new repo") 88 return errors.New("Error in config file") 89 } 90 // State of the repository set for further processing during the step 91 repoState = repoStateNew 92 log.Entry().Infof("gCTS Deploy : Creating Repository Step for repository : %v", config.Repository) 93 // Parse the configuration parameter to a format that is accepted by the gcts api 94 configurations, _ := splitConfigurationToMap(config.Configuration, *configurationMetadata) 95 createErr := createRepositoryForDeploy(&createRepoOptions, telemetryData, command, httpClient, configurations) 96 if createErr != nil { 97 //Dump error log (Log it) 98 log.Entry().WithError(createErr).Error("step execution failed at Create Repository") 99 return createErr 100 } 101 102 cloneRepoOptions := gctsCloneRepositoryOptions{ 103 Username: config.Username, 104 Password: config.Password, 105 Repository: config.Repository, 106 Host: config.Host, 107 Client: config.Client, 108 QueryParameters: config.QueryParameters, 109 SkipSSLVerification: config.SkipSSLVerification, 110 } 111 // No Import has to be set when there is a commit or branch parameter set 112 // This is required so that during the clone of the repo it is not imported into the system 113 // The import would be done at a later stage with the help of gcts deploy api call 114 if config.Branch != "" || config.Commit != "" { 115 setNoImportAndCloneRepoErr := setNoImportAndCloneRepo(config, &cloneRepoOptions, httpClient, telemetryData) 116 if setNoImportAndCloneRepoErr != nil { 117 log.Entry().WithError(setNoImportAndCloneRepoErr).Error("step execution failed") 118 return setNoImportAndCloneRepoErr 119 } 120 121 } else { 122 // Clone Repository and Exit the step since there is no commit or branch parameters provided 123 cloneErr := cloneRepository(&cloneRepoOptions, telemetryData, httpClient) 124 if cloneErr != nil { 125 // Dump Error Log 126 log.Entry().WithError(cloneErr).Error("step execution failed at Clone Repository") 127 return cloneErr 128 } 129 log.Entry().Infof("gCTS Deploy : Step has completed for the repository %v : ", config.Repository) 130 // End of the step. 131 return nil 132 } 133 log.Entry().Infof("gCTS Deploy : Reading repo information after cloning repository %v : ", config.Repository) 134 // Get the repository information for further processing of the step. 135 repoMetadataInitState, getRepositoryErr = getRepository(config, httpClient) 136 if getRepositoryErr != nil { 137 // Dump Error Log 138 log.Entry().WithError(getRepositoryErr).Error("step execution failed at get repository after clone") 139 return getRepositoryErr 140 } 141 currentBranch = repoMetadataInitState.Result.Branch 142 } else { 143 log.Entry().Infof("Repository %v already exists in the system, Checking for deploy scope", config.Repository) 144 // If deploy scope provided for an existing repository then deploy api is called and then execution ends 145 if config.Scope != "" { 146 log.Entry().Infof("Deploy scope exists for the repository in the configuration file") 147 log.Entry().Infof("gCTS Deploy: Deploying Commit to ABAP System for Repository %v with scope %v", config.Repository, config.Scope) 148 deployErr := deployCommitToAbapSystem(config, httpClient) 149 if deployErr != nil { 150 log.Entry().WithError(deployErr).Error("step execution failed at Deploying Commit to ABAP system.") 151 return deployErr 152 } 153 return nil 154 } 155 log.Entry().Infof("Deploy scope not set in the configuration file for repository : %v", config.Repository) 156 } 157 // branch to which the switching has to be done to 158 targetBranch := config.Branch 159 if config.Branch != "" { 160 // switch to a target branch, and if it fails rollback to the previous working state 161 _, switchBranchWithRollbackErr := switchBranchWithRollback(config, httpClient, currentBranch, targetBranch, repoState, repoMetadataInitState) 162 if switchBranchWithRollbackErr != nil { 163 return switchBranchWithRollbackErr 164 } 165 currentBranch = config.Branch 166 branchRollbackRequired = true 167 } 168 169 if config.Commit != "" { 170 // switch to a target commit and if it fails rollback to the previous working state 171 pullByCommitWithRollbackErr := pullByCommitWithRollback(config, telemetryData, command, httpClient, repoState, repoMetadataInitState, currentBranch, targetBranch, branchRollbackRequired) 172 if pullByCommitWithRollbackErr != nil { 173 return pullByCommitWithRollbackErr 174 } 175 } else { 176 // if commit parameter is not provided and its a new repo , then set config scope to "Current Commit" to be used with deploy api 177 if repoState == repoStateNew && (config.Commit != "" || config.Branch != "") { 178 log.Entry().Infof("Setting deploy scope as current commit") 179 config.Scope = "CRNTCOMMIT" 180 } 181 182 if config.Scope != "" { 183 removeNoImportAndDeployToSystemErr := removeNoImportAndDeployToSystem(config, httpClient) 184 if removeNoImportAndDeployToSystemErr != nil { 185 return removeNoImportAndDeployToSystemErr 186 } 187 // Step Execution Ends here 188 return nil 189 } 190 191 pullByCommitWithRollbackErr := pullByCommitWithRollback(config, telemetryData, command, httpClient, repoState, repoMetadataInitState, currentBranch, targetBranch, branchRollbackRequired) 192 if pullByCommitWithRollbackErr != nil { 193 return pullByCommitWithRollbackErr 194 } 195 } 196 // A deploy is done with scope current commit if the repository is a new repo and 197 // branch and a commit parameters where also provided 198 // This is required so that the code base is imported into the system because during the 199 // switch branch and pull by commit the no import flag was set as true 200 if repoState == repoStateNew { 201 log.Entry().Infof("Setting deploy scope as current commit") 202 config.Scope = "CRNTCOMMIT" 203 removeNoImportAndDeployToSystemErr := removeNoImportAndDeployToSystem(config, httpClient) 204 if removeNoImportAndDeployToSystemErr != nil { 205 return removeNoImportAndDeployToSystemErr 206 } 207 208 } 209 210 return nil 211 } 212 213 // Function to remove the VCS_NO_IMPORT flag and do deploy into the abap system 214 func removeNoImportAndDeployToSystem(config *gctsDeployOptions, httpClient piperhttp.Sender) error { 215 log.Entry().Infof("Removing VCS_NO_IMPORT configuration") 216 configToDelete := "VCS_NO_IMPORT" 217 deleteConfigKeyErr := deleteConfigKey(config, httpClient, configToDelete) 218 if deleteConfigKeyErr != nil { 219 log.Entry().WithError(deleteConfigKeyErr).Error("step execution failed at Set Config key for VCS_NO_IMPORT") 220 return deleteConfigKeyErr 221 } 222 // Get deploy scope and gctsDeploy 223 log.Entry().Infof("gCTS Deploy: Deploying Commit to ABAP System for Repository %v with scope %v", config.Repository, config.Scope) 224 deployErr := deployCommitToAbapSystem(config, httpClient) 225 if deployErr != nil { 226 log.Entry().WithError(deployErr).Error("step execution failed at Deploying Commit to ABAP system.") 227 return deployErr 228 } 229 return nil 230 } 231 232 // Function to pull by commit, it also does a rollback incase of errors during the pull 233 func pullByCommitWithRollback(config *gctsDeployOptions, telemetryData *telemetry.CustomData, command command.ExecRunner, 234 httpClient piperhttp.Sender, repoState string, repoMetadataInitState *getRepositoryResponseBody, 235 currentBranch string, targetBranch string, branchRollbackRequired bool) error { 236 237 log.Entry().Infof("gCTS Deploy: Pull by Commit step execution to commit %v", config.Commit) 238 pullByCommitErr := pullByCommit(config, telemetryData, command, httpClient) 239 if pullByCommitErr != nil { 240 log.Entry().WithError(pullByCommitErr).Error("step execution failed at Pull By Commit. Trying to rollback to last commit") 241 if config.Rollback { 242 //Rollback to last commit. 243 rollbackOptions := gctsRollbackOptions{ 244 Username: config.Username, 245 Password: config.Password, 246 Repository: config.Repository, 247 Host: config.Host, 248 Client: config.Client, 249 SkipSSLVerification: config.SkipSSLVerification, 250 } 251 rollbackErr := rollback(&rollbackOptions, telemetryData, command, httpClient) 252 if rollbackErr != nil { 253 log.Entry().WithError(rollbackErr).Error("step execution failed while rolling back commit") 254 return rollbackErr 255 } 256 if repoState == repoStateNew && branchRollbackRequired { 257 // Rollback branch 258 // Rollback branch. Resetting branches 259 targetBranch = repoMetadataInitState.Result.Branch 260 currentBranch = config.Branch 261 log.Entry().Errorf("Rolling Back from %v to %v", currentBranch, targetBranch) 262 switchBranch(config, httpClient, currentBranch, targetBranch) 263 } 264 } 265 return pullByCommitErr 266 } 267 return nil 268 269 } 270 271 // Function to switch branches, it also does a rollback incase of errors during the switch 272 func switchBranchWithRollback(config *gctsDeployOptions, httpClient piperhttp.Sender, currentBranch string, targetBranch string, repoState string, repoMetadataInitState *getRepositoryResponseBody) (*switchBranchResponseBody, error) { 273 var response *switchBranchResponseBody 274 response, switchBranchErr := switchBranch(config, httpClient, currentBranch, targetBranch) 275 if switchBranchErr != nil { 276 log.Entry().WithError(switchBranchErr).Error("step execution failed at Switch Branch") 277 if repoState == repoStateNew && config.Rollback { 278 // Rollback branch. Resetting branches 279 targetBranch = repoMetadataInitState.Result.Branch 280 currentBranch = config.Branch 281 log.Entry().WithError(switchBranchErr).Errorf("Rolling Back from %v to %v", currentBranch, targetBranch) 282 switchBranch(config, httpClient, currentBranch, targetBranch) 283 } 284 return nil, switchBranchErr 285 } 286 return response, nil 287 } 288 289 // Set VCS_NO_IMPORT flag to true and do a clone of the repo. This disables the repository objects to be pulled in to the system 290 func setNoImportAndCloneRepo(config *gctsDeployOptions, cloneRepoOptions *gctsCloneRepositoryOptions, httpClient piperhttp.Sender, telemetryData *telemetry.CustomData) error { 291 log.Entry().Infof("Setting VCS_NO_IMPORT to true") 292 noImportConfig := setConfigKeyBody{ 293 Key: "VCS_NO_IMPORT", 294 Value: "X", 295 } 296 setConfigKeyErr := setConfigKey(config, httpClient, &noImportConfig) 297 if setConfigKeyErr != nil { 298 log.Entry().WithError(setConfigKeyErr).Error("step execution failed at Set Config key for VCS_NO_IMPORT") 299 return setConfigKeyErr 300 } 301 cloneErr := cloneRepository(cloneRepoOptions, telemetryData, httpClient) 302 303 if cloneErr != nil { 304 log.Entry().WithError(cloneErr).Error("step execution failed at Clone Repository") 305 return cloneErr 306 } 307 return nil 308 } 309 310 // Function to switch branch 311 func switchBranch(config *gctsDeployOptions, httpClient piperhttp.Sender, currentBranch string, targetBranch string) (*switchBranchResponseBody, error) { 312 var response switchBranchResponseBody 313 log.Entry().Infof("gCTS Deploy : Switching branch for repository : %v, from branch: %v to %v", config.Repository, currentBranch, targetBranch) 314 requestURL := config.Host + 315 "/sap/bc/cts_abapvcs/repository/" + config.Repository + "/branches/" + currentBranch + 316 "/switch?branch=" + targetBranch + "&sap-client=" + config.Client 317 318 requestURL, urlErr := addQueryToURL(requestURL, config.QueryParameters) 319 320 if urlErr != nil { 321 322 return nil, urlErr 323 } 324 325 resp, httpErr := httpClient.SendRequest("GET", requestURL, nil, nil, nil) 326 defer func() { 327 if resp != nil && resp.Body != nil { 328 resp.Body.Close() 329 } 330 }() 331 if httpErr != nil { 332 _, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) 333 if errorDumpParseErr != nil { 334 return nil, errorDumpParseErr 335 } 336 return &response, httpErr 337 } else if resp == nil { 338 return &response, errors.New("did not retrieve a HTTP response") 339 } 340 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &response) 341 if parsingErr != nil { 342 return &response, parsingErr 343 } 344 log.Entry().Infof("Switched branches from %v to %v. The commits where switched from %v to %v", currentBranch, config.Branch, response.Result.FromCommit, response.Result.ToCommit) 345 return &response, nil 346 } 347 348 func deployCommitToAbapSystem(config *gctsDeployOptions, httpClient piperhttp.Sender) error { 349 var response getRepositoryResponseBody 350 deployRequestBody := deployCommitToAbapSystemBody{ 351 Scope: config.Scope, 352 } 353 log.Entry().Info("gCTS Deploy : Start of deploying commit to ABAP System.") 354 requestURL := config.Host + 355 "/sap/bc/cts_abapvcs/repository/" + config.Repository + 356 "/deploy?sap-client=" + config.Client 357 358 requestURL, urlErr := addQueryToURL(requestURL, config.QueryParameters) 359 360 if urlErr != nil { 361 362 return urlErr 363 } 364 365 reqBody := deployRequestBody 366 jsonBody, marshalErr := json.Marshal(reqBody) 367 if marshalErr != nil { 368 return errors.Wrapf(marshalErr, "Deploying repository to abap system failed json body marshalling") 369 } 370 header := make(http.Header) 371 header.Set("Content-Type", "application/json") 372 header.Add("Accept", "application/json") 373 resp, httpErr := httpClient.SendRequest("POST", requestURL, bytes.NewBuffer(jsonBody), header, nil) 374 defer func() { 375 if resp != nil && resp.Body != nil { 376 resp.Body.Close() 377 } 378 }() 379 if httpErr != nil { 380 _, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) 381 if errorDumpParseErr != nil { 382 return errorDumpParseErr 383 } 384 log.Entry().Error("Failed During Deploy to Abap system") 385 return httpErr 386 } 387 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &response) 388 if parsingErr != nil { 389 return parsingErr 390 } 391 log.Entry().Infof("Response for deploy command : %v", response.Result) 392 return nil 393 } 394 395 // Uses the repository details to check if the repository already exists in the system or not 396 func getRepository(config *gctsDeployOptions, httpClient piperhttp.Sender) (*getRepositoryResponseBody, error) { 397 var response getRepositoryResponseBody 398 requestURL := config.Host + 399 "/sap/bc/cts_abapvcs/repository/" + config.Repository + 400 "?sap-client=" + config.Client 401 402 requestURL, urlErr := addQueryToURL(requestURL, config.QueryParameters) 403 404 if urlErr != nil { 405 406 return nil, urlErr 407 } 408 409 resp, httpErr := httpClient.SendRequest("GET", requestURL, nil, nil, nil) 410 defer func() { 411 if resp != nil && resp.Body != nil { 412 resp.Body.Close() 413 } 414 }() 415 if httpErr != nil { 416 _, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) 417 if errorDumpParseErr != nil { 418 return nil, errorDumpParseErr 419 } 420 log.Entry().Infof("Error while repository Check : %v", httpErr) 421 return &response, httpErr 422 } else if resp == nil { 423 return &response, errors.New("did not retrieve a HTTP response") 424 } 425 426 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &response) 427 if parsingErr != nil { 428 return &response, parsingErr 429 } 430 return &response, nil 431 } 432 433 // Function to delete configuration key for repositories 434 func deleteConfigKey(deployConfig *gctsDeployOptions, httpClient piperhttp.Sender, configToDelete string) error { 435 log.Entry().Infof("gCTS Deploy : Delete configuration key %v", configToDelete) 436 requestURL := deployConfig.Host + 437 "/sap/bc/cts_abapvcs/repository/" + deployConfig.Repository + 438 "/config/" + configToDelete + "?sap-client=" + deployConfig.Client 439 440 requestURL, urlErr := addQueryToURL(requestURL, deployConfig.QueryParameters) 441 442 if urlErr != nil { 443 444 return urlErr 445 } 446 447 header := make(http.Header) 448 header.Set("Content-Type", "application/json") 449 header.Add("Accept", "application/json") 450 resp, httpErr := httpClient.SendRequest("DELETE", requestURL, nil, header, nil) 451 defer func() { 452 if resp != nil && resp.Body != nil { 453 resp.Body.Close() 454 } 455 }() 456 if httpErr != nil { 457 _, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) 458 if errorDumpParseErr != nil { 459 return errorDumpParseErr 460 } 461 log.Entry().Error("Failure during deletion of configuration value") 462 return httpErr 463 } 464 log.Entry().Infof("gCTS Deploy : Delete configuration key %v successful", configToDelete) 465 return nil 466 } 467 468 // Function to set configuration key for repositories 469 func setConfigKey(deployConfig *gctsDeployOptions, httpClient piperhttp.Sender, configToSet *setConfigKeyBody) error { 470 log.Entry().Infof("gCTS Deploy : Start of set configuration key %v and value %v", configToSet.Key, configToSet.Value) 471 requestURL := deployConfig.Host + 472 "/sap/bc/cts_abapvcs/repository/" + deployConfig.Repository + 473 "/config?sap-client=" + deployConfig.Client 474 475 requestURL, urlErr := addQueryToURL(requestURL, deployConfig.QueryParameters) 476 477 if urlErr != nil { 478 479 return urlErr 480 } 481 482 reqBody := configToSet 483 jsonBody, marshalErr := json.Marshal(reqBody) 484 if marshalErr != nil { 485 return errors.Wrapf(marshalErr, "Setting config key: %v and value: %v on the ABAP system %v failed", configToSet.Key, configToSet.Value, deployConfig.Host) 486 } 487 header := make(http.Header) 488 header.Set("Content-Type", "application/json") 489 header.Add("Accept", "application/json") 490 resp, httpErr := httpClient.SendRequest("POST", requestURL, bytes.NewBuffer(jsonBody), header, nil) 491 defer func() { 492 if resp != nil && resp.Body != nil { 493 resp.Body.Close() 494 } 495 }() 496 if httpErr != nil { 497 _, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) 498 if errorDumpParseErr != nil { 499 return errorDumpParseErr 500 } 501 log.Entry().Error("Failure during setting configuration value") 502 return httpErr 503 } 504 log.Entry(). 505 WithField("repository", deployConfig.Repository). 506 Infof("successfully set configuration value key %v and value %v", configToSet.Key, configToSet.Value) 507 return nil 508 } 509 510 func pullByCommit(config *gctsDeployOptions, telemetryData *telemetry.CustomData, command command.ExecRunner, httpClient piperhttp.Sender) error { 511 512 cookieJar, cookieErr := cookiejar.New(nil) 513 if cookieErr != nil { 514 return errors.Wrap(cookieErr, "creating a cookie jar failed") 515 } 516 clientOptions := piperhttp.ClientOptions{ 517 CookieJar: cookieJar, 518 Username: config.Username, 519 Password: config.Password, 520 MaxRetries: -1, 521 TransportSkipVerification: config.SkipSSLVerification, 522 } 523 httpClient.SetOptions(clientOptions) 524 525 requestURL := config.Host + 526 "/sap/bc/cts_abapvcs/repository/" + config.Repository + 527 "/pullByCommit?sap-client=" + config.Client + "&request=" + config.Commit 528 529 requestURL, urlErr := addQueryToURL(requestURL, config.QueryParameters) 530 531 if urlErr != nil { 532 533 return urlErr 534 } 535 536 if config.Commit != "" { 537 log.Entry().Infof("preparing to deploy specified commit %v", config.Commit) 538 params := url.Values{} 539 params.Add("request", config.Commit) 540 requestURL = requestURL + "&" + params.Encode() 541 } 542 543 resp, httpErr := httpClient.SendRequest("GET", requestURL, nil, nil, nil) 544 545 defer func() { 546 if resp != nil && resp.Body != nil { 547 resp.Body.Close() 548 } 549 }() 550 551 if httpErr != nil { 552 _, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) 553 if errorDumpParseErr != nil { 554 return errorDumpParseErr 555 } 556 return httpErr 557 } else if resp == nil { 558 return errors.New("did not retrieve a HTTP response") 559 } 560 561 bodyText, readErr := io.ReadAll(resp.Body) 562 563 if readErr != nil { 564 return errors.Wrapf(readErr, "HTTP response body could not be read") 565 } 566 567 response, parsingErr := gabs.ParseJSON([]byte(bodyText)) 568 569 if parsingErr != nil { 570 return errors.Wrapf(parsingErr, "HTTP response body could not be parsed as JSON: %v", string(bodyText)) 571 } 572 573 log.Entry(). 574 WithField("repository", config.Repository). 575 Infof("successfully deployed commit %v (previous commit was %v)", response.Path("toCommit").Data().(string), response.Path("fromCommit").Data().(string)) 576 return nil 577 } 578 579 func createRepositoryForDeploy(config *gctsCreateRepositoryOptions, telemetryData *telemetry.CustomData, command command.ExecRunner, httpClient piperhttp.Sender, repositoryConfig []repositoryConfiguration) error { 580 581 cookieJar, cookieErr := cookiejar.New(nil) 582 if cookieErr != nil { 583 return errors.Wrapf(cookieErr, "creating repository on the ABAP system %v failed", config.Host) 584 } 585 clientOptions := piperhttp.ClientOptions{ 586 CookieJar: cookieJar, 587 Username: config.Username, 588 Password: config.Password, 589 MaxRetries: -1, 590 TransportSkipVerification: config.SkipSSLVerification, 591 } 592 httpClient.SetOptions(clientOptions) 593 594 type repoData struct { 595 RID string `json:"rid"` 596 Name string `json:"name"` 597 Role string `json:"role"` 598 Type string `json:"type"` 599 VSID string `json:"vsid"` 600 RemoteRepositoryURL string `json:"url"` 601 Config []repositoryConfiguration `json:"config"` 602 } 603 604 type createRequestBody struct { 605 Repository string `json:"repository"` 606 Data repoData `json:"data"` 607 } 608 609 reqBody := createRequestBody{ 610 Repository: config.Repository, 611 Data: repoData{ 612 RID: config.Repository, 613 Name: config.Repository, 614 Role: config.Role, 615 Type: config.Type, 616 VSID: config.VSID, 617 RemoteRepositoryURL: config.RemoteRepositoryURL, 618 Config: repositoryConfig, 619 }, 620 } 621 jsonBody, marshalErr := json.Marshal(reqBody) 622 623 if marshalErr != nil { 624 return errors.Wrapf(marshalErr, "creating repository on the ABAP system %v failed", config.Host) 625 } 626 627 header := make(http.Header) 628 header.Set("Content-Type", "application/json") 629 header.Add("Accept", "application/json") 630 631 url := config.Host + "/sap/bc/cts_abapvcs/repository?sap-client=" + config.Client 632 633 url, urlErr := addQueryToURL(url, config.QueryParameters) 634 635 if urlErr != nil { 636 637 return urlErr 638 } 639 640 resp, httpErr := httpClient.SendRequest("POST", url, bytes.NewBuffer(jsonBody), header, nil) 641 642 defer func() { 643 if resp != nil && resp.Body != nil { 644 resp.Body.Close() 645 } 646 }() 647 648 if resp == nil { 649 return errors.Errorf("creating repository on the ABAP system %v failed: %v", config.Host, httpErr) 650 } 651 652 if httpErr != nil { 653 response, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) 654 if errorDumpParseErr != nil { 655 return errorDumpParseErr 656 } 657 if resp.StatusCode == 500 { 658 if response.Exception == "Repository already exists" { 659 log.Entry(). 660 WithField("repository", config.Repository). 661 Infof("the repository already exists on the ABAP system %v", config.Host) 662 return nil 663 } 664 } 665 log.Entry().Errorf("a HTTP error occurred! Response body: %v", response) 666 return errors.Wrapf(httpErr, "creating repository on the ABAP system %v failed", config.Host) 667 } 668 669 log.Entry(). 670 WithField("repository", config.Repository). 671 Infof("successfully created the repository on ABAP system %v", config.Host) 672 return nil 673 } 674 675 func getConfigurationMetadata(config *gctsDeployOptions, httpClient piperhttp.Sender) (*configurationMetadataBody, error) { 676 var response configurationMetadataBody 677 log.Entry().Infof("Starting to retrieve configuration metadata from the system") 678 requestURL := config.Host + 679 "/sap/bc/cts_abapvcs/config?sap-client=" + config.Client 680 681 requestURL, urlErr := addQueryToURL(requestURL, config.QueryParameters) 682 683 if urlErr != nil { 684 685 return nil, urlErr 686 } 687 688 resp, httpErr := httpClient.SendRequest("GET", requestURL, nil, nil, nil) 689 defer func() { 690 if resp != nil && resp.Body != nil { 691 resp.Body.Close() 692 } 693 }() 694 if httpErr != nil { 695 _, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) 696 if errorDumpParseErr != nil { 697 return nil, errorDumpParseErr 698 } 699 log.Entry().Infof("Error while repository Check : %v", httpErr) 700 return &response, httpErr 701 } else if resp == nil { 702 return &response, errors.New("did not retrieve a HTTP response") 703 } 704 705 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &response) 706 if parsingErr != nil { 707 return &response, parsingErr 708 } 709 log.Entry().Infof("System Available for further step processing. The configuration metadata was successfully retrieved.") 710 return &response, nil 711 } 712 713 func splitConfigurationToMap(inputConfigMap map[string]interface{}, configMetadataInSystem configurationMetadataBody) ([]repositoryConfiguration, error) { 714 log.Entry().Infof("Parsing the configurations from the yml file") 715 var configurations []repositoryConfiguration 716 for key, value := range inputConfigMap { 717 foundConfigMetadata, _ := findConfigurationMetadata(key, configMetadataInSystem) 718 configValue := fmt.Sprint(value) 719 if (configMetadata{}) != foundConfigMetadata { 720 if foundConfigMetadata.Datatype == "BOOLEAN" && foundConfigMetadata.Example == "X" { 721 if configValue == "false" || configValue == "" { 722 configValue = "" 723 } else if configValue == "true" || configValue == "X" { 724 configValue = "X" 725 } 726 } 727 } 728 configuration := repositoryConfiguration{ 729 Key: key, 730 Value: configValue, 731 } 732 configurations = append(configurations, configuration) 733 734 } 735 log.Entry().Infof("The Configurations for the repoistory creation are : %v", configurations) 736 return configurations, nil 737 } 738 739 func findConfigurationMetadata(configToFind string, configurationsAvailable configurationMetadataBody) (configMetadata, error) { 740 var configStruct configMetadata 741 for _, config := range configurationsAvailable.Config { 742 if config.Ckey == configToFind { 743 return config, nil 744 } 745 } 746 return configStruct, nil 747 } 748 749 // Error handling for failure responses from the gcts api calls 750 func parseErrorDumpFromResponseBody(responseBody *http.Response) (*errorLogBody, error) { 751 var errorDump errorLogBody 752 parsingErr := piperhttp.ParseHTTPResponseBodyJSON(responseBody, &errorDump) 753 if parsingErr != nil { 754 return &errorDump, parsingErr 755 } 756 for _, errorLogData := range errorDump.ErrorLog { 757 log.Entry().Errorf("Time: %v, User: %v, Section: %v, Action: %v, Severity: %v, Message: %v", 758 errorLogData.Time, errorLogData.User, errorLogData.Section, 759 errorLogData.Action, errorLogData.Severity, errorLogData.Message) 760 for _, protocolErrorData := range errorLogData.Protocol { 761 log.Entry().Errorf("Type: %v", protocolErrorData.Type) 762 for _, protocols := range protocolErrorData.Protocol { 763 if strings.Contains(protocols, "4 ETW000 ") { 764 protocols = strings.ReplaceAll(protocols, "4 ETW000 ", "") 765 } else if strings.Contains(protocols, "4EETW000 ") { 766 protocols = strings.ReplaceAll(protocols, "4EETW000 ", "ERROR: ") 767 } 768 log.Entry().Error(protocols) 769 } 770 } 771 } 772 return &errorDump, nil 773 } 774 775 func addQueryToURL(requestURL string, keyValue map[string]interface{}) (string, error) { 776 777 var formattedURL string 778 formattedURL = requestURL 779 if keyValue != nil { 780 if strings.Contains(requestURL, "?") { 781 for key, value := range keyValue { 782 configValue := fmt.Sprint(value) 783 formattedURL = formattedURL + "&" + key + "=" + configValue 784 } 785 } else { 786 i := 0 787 for key, value := range keyValue { 788 configValue := fmt.Sprint(value) 789 if i == 0 { 790 formattedURL = requestURL + "?" + key + "=" + configValue 791 } else { 792 formattedURL = formattedURL + "&" + key + "=" + configValue 793 } 794 i++ 795 } 796 } 797 } 798 if strings.Count(formattedURL, "") > 2001 { 799 800 log.Entry().Error("Url endpoint is longer than 2000 characters!") 801 return formattedURL, errors.New("Url endpoint is longer than 2000 characters!") 802 803 } 804 805 return formattedURL, nil 806 } 807 808 type repositoryConfiguration struct { 809 Key string `json:"key"` 810 Value string `json:"value"` 811 } 812 813 type getRepositoryResponseBody struct { 814 Result struct { 815 Rid string `json:"rid"` 816 Name string `json:"name"` 817 Role string `json:"role"` 818 Vsid string `json:"vsid"` 819 Status string `json:"status"` 820 Branch string `json:"branch"` 821 Url string `json:"url"` 822 Config []struct { 823 Key string `json:"key"` 824 Value string `json:"value"` 825 Category string `json:"category"` 826 } `json:"config"` 827 Objects int64 `json:"objects"` 828 CurrentCommit string `json:"currentCommit"` 829 Connection string `json:"connection"` 830 } `json:"result"` 831 } 832 833 type setConfigKeyBody struct { 834 Key string `json:"key"` 835 Value string `json:"value"` 836 } 837 838 type switchBranchResponseBody struct { 839 Result struct { 840 FromCommit string `json:"fromCommit"` 841 ToCommit string `json:"ToCommit"` 842 } `json:"result"` 843 Log []struct { 844 Time string `json:"time"` 845 User string `json:"user"` 846 Section string `json:"section"` 847 Action string `json:"Action"` 848 Severity string `json:"Severity"` 849 Message string `json:"Message"` 850 } `json:"log"` 851 } 852 853 type deployCommitToAbapSystemBody struct { 854 Repository string `json:"repository"` 855 Scope string `json:"scope"` 856 Commit string `json:"commit"` 857 Objects []struct { 858 Object string `json:"object"` 859 Type string `json:"type"` 860 User string `json:"user"` 861 Pgmid string `json:"pgmid"` 862 Keys []struct { 863 Tabname string `json:"tabname"` 864 Columns []struct { 865 Key string `json:"key"` 866 Field string `json:"field"` 867 Value string `json:"value"` 868 Type string `json:"type"` 869 Inttype string `json:"inttype"` 870 Length string `json:"length"` 871 } 872 } 873 } `json:"objects"` 874 } 875 876 type configMetadata struct { 877 Ckey string `json:"ckey"` 878 Ctype string `json:"ctype"` 879 Cvisible string `json:"cvisible"` 880 Datatype string `json:"datatype"` 881 DefaultValue string `json:"defaultValue"` 882 Description string `json:"description"` 883 Category string `json:"category"` 884 UiElement string `json:"uiElement"` 885 Example string `json:"example"` 886 } 887 888 type configurationMetadataBody struct { 889 Config []configMetadata `json:"config"` 890 } 891 892 type errorProtocolbody struct { 893 Type string `json:"type"` 894 Protocol []string `json:"protocol"` 895 } 896 897 type errorLog struct { 898 Time int `json:"time"` 899 User string `json:"user"` 900 Section string `json:"section"` 901 Action string `json:"action"` 902 Severity string `json:"severity"` 903 Message string `json:"message"` 904 Protocol []errorProtocolbody `json:"protocol"` 905 } 906 907 type errorLogBody struct { 908 ErrorLog []errorLog `json:"errorLog"` 909 Exception string `json:"exception"` 910 }