github.com/SAP/jenkins-library@v1.362.0/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  }