github.com/jaylevin/jenkins-library@v1.230.4/cmd/gctsDeploy.go (about)

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