You've already forked sap-jenkins-library
							
							
				mirror of
				https://github.com/SAP/jenkins-library.git
				synced 2025-10-30 23:57:50 +02:00 
			
		
		
		
	Fix(gctsDeploy) Refactoring gctsDeploy step (#2789)
* Changed gcts deploy step to include create, clone and switching branches * Added create only flag for repository * Changed the logic of Rollback * Added more logs to deployCommitToAbapSystem * Changes to deploy to abap system * Changes to deploy to abap system * Changes to condition for pullbycommit * Added Current commit deploy scope handling * Changed VCS_NO_IMPORT to take abap bool * Added delete config functionality * Functionality to parse boolean configuration * Fix to get config metadata url * Added additional error messages for switch branch * Better error dump handling * Better error dump handling contd * Added dump errors to all http requests * Error logging changes * More Unit Tests * Added more logs * Updated docs for gCTS deploy * Added scope in documentation * Removal of some nested loops, fix of unit tests * Documentation changes and more comments in the code Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>
This commit is contained in:
		| @@ -1,18 +1,32 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"net/http/cookiejar" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/Jeffail/gabs/v2" | ||||
| 	"github.com/SAP/jenkins-library/pkg/command" | ||||
| 	piperhttp "github.com/SAP/jenkins-library/pkg/http" | ||||
| 	"github.com/SAP/jenkins-library/pkg/log" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| const repoStateExists = "RepoExists" | ||||
| const repoStateNew = "RepoNew" | ||||
|  | ||||
| func gctsDeploy(config gctsDeployOptions, telemetryData *telemetry.CustomData) { | ||||
| 	// for command execution use Command | ||||
| 	c := command.Command{} | ||||
| 	// reroute command output to logging framework | ||||
| 	c.Stdout(log.Writer()) | ||||
| 	c.Stderr(log.Writer()) | ||||
|  | ||||
| 	// for http calls import  piperhttp "github.com/SAP/jenkins-library/pkg/http" | ||||
| 	// and use a  &piperhttp.Client{} in a custom system | ||||
| @@ -20,13 +34,434 @@ func gctsDeploy(config gctsDeployOptions, telemetryData *telemetry.CustomData) { | ||||
| 	httpClient := &piperhttp.Client{} | ||||
|  | ||||
| 	// error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end | ||||
| 	err := deployCommit(&config, nil, httpClient) | ||||
| 	err := gctsDeployRepository(&config, telemetryData, &c, httpClient) | ||||
| 	if err != nil { | ||||
| 		log.Entry().WithError(err).Fatal("step execution failed") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func deployCommit(config *gctsDeployOptions, telemetryData *telemetry.CustomData, httpClient piperhttp.Sender) error { | ||||
| func gctsDeployRepository(config *gctsDeployOptions, telemetryData *telemetry.CustomData, command command.ExecRunner, httpClient piperhttp.Sender) error { | ||||
| 	cookieJar, cookieErr := cookiejar.New(nil) | ||||
| 	repoState := repoStateExists | ||||
| 	branchRollbackRequired := false | ||||
| 	if cookieErr != nil { | ||||
| 		return errors.Wrap(cookieErr, "creating a cookie jar failed") | ||||
| 	} | ||||
| 	clientOptions := piperhttp.ClientOptions{ | ||||
| 		CookieJar: cookieJar, | ||||
| 		Username:  config.Username, | ||||
| 		Password:  config.Password, | ||||
| 	} | ||||
| 	httpClient.SetOptions(clientOptions) | ||||
| 	log.Entry().Infof("Start of gCTS Deploy Step with Configuration Values: %v", config) | ||||
| 	configurationMetadata, getConfigMetadataErr := getConfigurationMetadata(config, httpClient) | ||||
|  | ||||
| 	if getConfigMetadataErr != nil { | ||||
| 		log.Entry().WithError(getConfigMetadataErr).Error("step execution failed at configuration metadata retrieval. Please Check if system is up!.") | ||||
| 		return getConfigMetadataErr | ||||
| 	} | ||||
|  | ||||
| 	createRepoOptions := gctsCreateRepositoryOptions{ | ||||
| 		Username:            config.Username, | ||||
| 		Password:            config.Password, | ||||
| 		Repository:          config.Repository, | ||||
| 		Host:                config.Host, | ||||
| 		Client:              config.Client, | ||||
| 		RemoteRepositoryURL: config.RemoteRepositoryURL, | ||||
| 		Role:                config.Role, | ||||
| 		VSID:                config.VSID, | ||||
| 		Type:                config.Type, | ||||
| 	} | ||||
| 	log.Entry().Infof("gCTS Deploy : Checking if repository %v already exists", config.Repository) | ||||
| 	repoMetadataInitState, getRepositoryErr := getRepository(config, httpClient) | ||||
| 	currentBranch := repoMetadataInitState.Result.Branch | ||||
| 	// If Repository does not exist in the system then Create and Clone Repository | ||||
| 	if getRepositoryErr != nil { | ||||
| 		// If scope is set for a new repository then creation/cloning of the repository cannot be done | ||||
| 		if config.Scope != "" { | ||||
| 			log.Entry().Error("Error during deploy : deploy scope cannot be provided while deploying a new repo") | ||||
| 			return errors.New("Error in config file") | ||||
| 		} | ||||
| 		// State of the repository set for further processing during the step | ||||
| 		repoState = repoStateNew | ||||
| 		log.Entry().Infof("gCTS Deploy : Creating Repository Step for repository : %v", config.Repository) | ||||
| 		// Parse the configuration parameter to a format that is accepted by the gcts api | ||||
| 		configurations, _ := splitConfigurationToMap(config.Configuration, *configurationMetadata) | ||||
| 		createErr := createRepositoryForDeploy(&createRepoOptions, telemetryData, command, httpClient, configurations) | ||||
| 		if createErr != nil { | ||||
| 			//Dump error log (Log it) | ||||
| 			log.Entry().WithError(createErr).Error("step execution failed at Create Repository") | ||||
| 			return createErr | ||||
| 		} | ||||
|  | ||||
| 		cloneRepoOptions := gctsCloneRepositoryOptions{ | ||||
| 			Username:   config.Username, | ||||
| 			Password:   config.Password, | ||||
| 			Repository: config.Repository, | ||||
| 			Host:       config.Host, | ||||
| 			Client:     config.Client, | ||||
| 		} | ||||
| 		// No Import has to be set when there is a commit or branch parameter set | ||||
| 		// This is required so that during the clone of the repo it is not imported into the system | ||||
| 		// The import would be done at a later stage with the help of gcts deploy api call | ||||
| 		if config.Branch != "" || config.Commit != "" { | ||||
| 			setNoImportAndCloneRepoErr := setNoImportAndCloneRepo(config, &cloneRepoOptions, httpClient, telemetryData) | ||||
| 			if setNoImportAndCloneRepoErr != nil { | ||||
| 				log.Entry().WithError(setNoImportAndCloneRepoErr).Error("step execution failed") | ||||
| 				return setNoImportAndCloneRepoErr | ||||
| 			} | ||||
|  | ||||
| 		} else { | ||||
| 			// Clone Repository and Exit the step since there is no commit or branch parameters provided | ||||
| 			cloneErr := cloneRepository(&cloneRepoOptions, telemetryData, httpClient) | ||||
| 			if cloneErr != nil { | ||||
| 				// Dump Error Log | ||||
| 				log.Entry().WithError(cloneErr).Error("step execution failed at Clone Repository") | ||||
| 				return cloneErr | ||||
| 			} | ||||
| 			log.Entry().Infof("gCTS Deploy : Step has completed for the repository %v : ", config.Repository) | ||||
| 			// End of the step. | ||||
| 			return nil | ||||
| 		} | ||||
| 		log.Entry().Infof("gCTS Deploy : Reading repo information after cloning repository %v : ", config.Repository) | ||||
| 		// Get the repository information for further processing of the step. | ||||
| 		repoMetadataInitState, getRepositoryErr = getRepository(config, httpClient) | ||||
| 		if getRepositoryErr != nil { | ||||
| 			// Dump Error Log | ||||
| 			log.Entry().WithError(getRepositoryErr).Error("step execution failed at get repository after clone") | ||||
| 			return getRepositoryErr | ||||
| 		} | ||||
| 		currentBranch = repoMetadataInitState.Result.Branch | ||||
| 	} else { | ||||
| 		log.Entry().Infof("Repository %v already exists in the system, Checking for deploy scope", config.Repository) | ||||
| 		// If deploy scope provided for an existing repository then deploy api is called and then execution ends | ||||
| 		if config.Scope != "" { | ||||
| 			log.Entry().Infof("Deploy scope exists for the repository in the configuration file") | ||||
| 			log.Entry().Infof("gCTS Deploy: Deploying Commit to ABAP System for Repository %v with scope %v", config.Repository, config.Scope) | ||||
| 			deployErr := deployCommitToAbapSystem(config, httpClient) | ||||
| 			if deployErr != nil { | ||||
| 				log.Entry().WithError(deployErr).Error("step execution failed at Deploying Commit to ABAP system.") | ||||
| 				return deployErr | ||||
| 			} | ||||
| 			return nil | ||||
| 		} | ||||
| 		log.Entry().Infof("Deploy scope not set in the configuration file for repository : %v", config.Repository) | ||||
| 	} | ||||
| 	// branch to which the switching has to be done to | ||||
| 	targetBranch := config.Branch | ||||
| 	if config.Branch != "" { | ||||
| 		// switch to a target branch, and if it fails rollback to the previous working state | ||||
| 		_, switchBranchWithRollbackErr := switchBranchWithRollback(config, httpClient, currentBranch, targetBranch, repoState, repoMetadataInitState) | ||||
| 		if switchBranchWithRollbackErr != nil { | ||||
| 			return switchBranchWithRollbackErr | ||||
| 		} | ||||
| 		currentBranch = config.Branch | ||||
| 		branchRollbackRequired = true | ||||
| 	} | ||||
|  | ||||
| 	if config.Commit != "" { | ||||
| 		// switch to a target commit and if it fails rollback to the previous working state | ||||
| 		pullByCommitWithRollbackErr := pullByCommitWithRollback(config, telemetryData, command, httpClient, repoState, repoMetadataInitState, currentBranch, targetBranch, branchRollbackRequired) | ||||
| 		if pullByCommitWithRollbackErr != nil { | ||||
| 			return pullByCommitWithRollbackErr | ||||
| 		} | ||||
| 	} else { | ||||
| 		// if commit parameter is not provided and its a new repo , then set config scope to "Current Commit" to be used with deploy api | ||||
| 		if repoState == repoStateNew && (config.Commit != "" || config.Branch != "") { | ||||
| 			log.Entry().Infof("Setting deploy scope as current commit") | ||||
| 			config.Scope = "CRNTCOMMIT" | ||||
| 		} | ||||
|  | ||||
| 		if config.Scope != "" { | ||||
| 			removeNoImportAndDeployToSystemErr := removeNoImportAndDeployToSystem(config, httpClient) | ||||
| 			if removeNoImportAndDeployToSystemErr != nil { | ||||
| 				return removeNoImportAndDeployToSystemErr | ||||
| 			} | ||||
| 			// Step Execution Ends here | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		pullByCommitWithRollbackErr := pullByCommitWithRollback(config, telemetryData, command, httpClient, repoState, repoMetadataInitState, currentBranch, targetBranch, branchRollbackRequired) | ||||
| 		if pullByCommitWithRollbackErr != nil { | ||||
| 			return pullByCommitWithRollbackErr | ||||
| 		} | ||||
| 	} | ||||
| 	// A deploy is done with scope current commit if the repository is a new repo and | ||||
| 	// branch and a commit parameters where also provided | ||||
| 	// This is required so that the code base is imported into the system because during the | ||||
| 	// switch branch and pull by commit the no import flag was set as true | ||||
| 	if repoState == repoStateNew { | ||||
| 		log.Entry().Infof("Setting deploy scope as current commit") | ||||
| 		config.Scope = "CRNTCOMMIT" | ||||
| 		removeNoImportAndDeployToSystemErr := removeNoImportAndDeployToSystem(config, httpClient) | ||||
| 		if removeNoImportAndDeployToSystemErr != nil { | ||||
| 			return removeNoImportAndDeployToSystemErr | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Function to remove the VCS_NO_IMPORT flag and do deploy into the abap system | ||||
| func removeNoImportAndDeployToSystem(config *gctsDeployOptions, httpClient piperhttp.Sender) error { | ||||
| 	log.Entry().Infof("Removing VCS_NO_IMPORT configuration") | ||||
| 	configToDelete := "VCS_NO_IMPORT" | ||||
| 	deleteConfigKeyErr := deleteConfigKey(config, httpClient, configToDelete) | ||||
| 	if deleteConfigKeyErr != nil { | ||||
| 		log.Entry().WithError(deleteConfigKeyErr).Error("step execution failed at Set Config key for VCS_NO_IMPORT") | ||||
| 		return deleteConfigKeyErr | ||||
| 	} | ||||
| 	// Get deploy scope and gctsDeploy | ||||
| 	log.Entry().Infof("gCTS Deploy: Deploying Commit to ABAP System for Repository %v with scope %v", config.Repository, config.Scope) | ||||
| 	deployErr := deployCommitToAbapSystem(config, httpClient) | ||||
| 	if deployErr != nil { | ||||
| 		log.Entry().WithError(deployErr).Error("step execution failed at Deploying Commit to ABAP system.") | ||||
| 		return deployErr | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Function to pull by commit, it also does a rollback incase of errors during the pull | ||||
| func pullByCommitWithRollback(config *gctsDeployOptions, telemetryData *telemetry.CustomData, command command.ExecRunner, | ||||
| 	httpClient piperhttp.Sender, repoState string, repoMetadataInitState *getRepositoryResponseBody, | ||||
| 	currentBranch string, targetBranch string, branchRollbackRequired bool) error { | ||||
|  | ||||
| 	log.Entry().Infof("gCTS Deploy: Pull by Commit step execution to commit %v", config.Commit) | ||||
| 	pullByCommitErr := pullByCommit(config, telemetryData, command, httpClient) | ||||
| 	if pullByCommitErr != nil { | ||||
| 		log.Entry().WithError(pullByCommitErr).Error("step execution failed at Pull By Commit. Trying to rollback to last commit") | ||||
| 		if config.Rollback { | ||||
| 			//Rollback to last commit. | ||||
| 			rollbackOptions := gctsRollbackOptions{ | ||||
| 				Username:   config.Username, | ||||
| 				Password:   config.Password, | ||||
| 				Repository: config.Repository, | ||||
| 				Host:       config.Host, | ||||
| 				Client:     config.Client, | ||||
| 			} | ||||
| 			rollbackErr := rollback(&rollbackOptions, telemetryData, command, httpClient) | ||||
| 			if rollbackErr != nil { | ||||
| 				log.Entry().WithError(rollbackErr).Error("step execution failed while rolling back commit") | ||||
| 				return rollbackErr | ||||
| 			} | ||||
| 			if repoState == repoStateNew && branchRollbackRequired { | ||||
| 				// Rollback branch | ||||
| 				// Rollback branch. Resetting branches | ||||
| 				targetBranch = repoMetadataInitState.Result.Branch | ||||
| 				currentBranch = config.Branch | ||||
| 				log.Entry().Errorf("Rolling Back from %v to %v", currentBranch, targetBranch) | ||||
| 				switchBranch(config, httpClient, currentBranch, targetBranch) | ||||
| 			} | ||||
| 		} | ||||
| 		return pullByCommitErr | ||||
| 	} | ||||
| 	return nil | ||||
|  | ||||
| } | ||||
|  | ||||
| // Function to switch branches, it also does a rollback incase of errors during the switch | ||||
| func switchBranchWithRollback(config *gctsDeployOptions, httpClient piperhttp.Sender, currentBranch string, targetBranch string, repoState string, repoMetadataInitState *getRepositoryResponseBody) (*switchBranchResponseBody, error) { | ||||
| 	var response *switchBranchResponseBody | ||||
| 	response, switchBranchErr := switchBranch(config, httpClient, currentBranch, targetBranch) | ||||
| 	if switchBranchErr != nil { | ||||
| 		log.Entry().WithError(switchBranchErr).Error("step execution failed at Switch Branch") | ||||
| 		if repoState == repoStateNew && config.Rollback { | ||||
| 			// Rollback branch. Resetting branches | ||||
| 			targetBranch = repoMetadataInitState.Result.Branch | ||||
| 			currentBranch = config.Branch | ||||
| 			log.Entry().WithError(switchBranchErr).Errorf("Rolling Back from %v to %v", currentBranch, targetBranch) | ||||
| 			switchBranch(config, httpClient, currentBranch, targetBranch) | ||||
| 		} | ||||
| 		return nil, switchBranchErr | ||||
| 	} | ||||
| 	return response, nil | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| func setNoImportAndCloneRepo(config *gctsDeployOptions, cloneRepoOptions *gctsCloneRepositoryOptions, httpClient piperhttp.Sender, telemetryData *telemetry.CustomData) error { | ||||
| 	log.Entry().Infof("Setting VCS_NO_IMPORT to true") | ||||
| 	noImportConfig := setConfigKeyBody{ | ||||
| 		Key:   "VCS_NO_IMPORT", | ||||
| 		Value: "X", | ||||
| 	} | ||||
| 	setConfigKeyErr := setConfigKey(config, httpClient, &noImportConfig) | ||||
| 	if setConfigKeyErr != nil { | ||||
| 		log.Entry().WithError(setConfigKeyErr).Error("step execution failed at Set Config key for VCS_NO_IMPORT") | ||||
| 		return setConfigKeyErr | ||||
| 	} | ||||
| 	cloneErr := cloneRepository(cloneRepoOptions, telemetryData, httpClient) | ||||
|  | ||||
| 	if cloneErr != nil { | ||||
| 		log.Entry().WithError(cloneErr).Error("step execution failed at Clone Repository") | ||||
| 		return cloneErr | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Function to switch branch | ||||
| func switchBranch(config *gctsDeployOptions, httpClient piperhttp.Sender, currentBranch string, targetBranch string) (*switchBranchResponseBody, error) { | ||||
| 	var response switchBranchResponseBody | ||||
| 	log.Entry().Infof("gCTS Deploy : Switching branch for repository : %v, from branch: %v to %v", config.Repository, currentBranch, targetBranch) | ||||
| 	requestURL := config.Host + | ||||
| 		"/sap/bc/cts_abapvcs/repository/" + config.Repository + "/branches/" + currentBranch + | ||||
| 		"/switch?branch=" + targetBranch + "&sap-client=" + config.Client | ||||
| 	resp, httpErr := httpClient.SendRequest("GET", requestURL, nil, nil, nil) | ||||
| 	defer func() { | ||||
| 		if resp != nil && resp.Body != nil { | ||||
| 			resp.Body.Close() | ||||
| 		} | ||||
| 	}() | ||||
| 	if httpErr != nil { | ||||
| 		_, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) | ||||
| 		if errorDumpParseErr != nil { | ||||
| 			return nil, errorDumpParseErr | ||||
| 		} | ||||
| 		return &response, httpErr | ||||
| 	} else if resp == nil { | ||||
| 		return &response, errors.New("did not retrieve a HTTP response") | ||||
| 	} | ||||
| 	parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &response) | ||||
| 	if parsingErr != nil { | ||||
| 		return &response, parsingErr | ||||
| 	} | ||||
| 	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) | ||||
| 	return &response, nil | ||||
| } | ||||
|  | ||||
| func deployCommitToAbapSystem(config *gctsDeployOptions, httpClient piperhttp.Sender) error { | ||||
| 	var response getRepositoryResponseBody | ||||
| 	deployRequestBody := deployCommitToAbapSystemBody{ | ||||
| 		Scope: config.Scope, | ||||
| 	} | ||||
| 	log.Entry().Info("gCTS Deploy : Start of deploying commit to ABAP System.") | ||||
| 	requestURL := config.Host + | ||||
| 		"/sap/bc/cts_abapvcs/repository/" + config.Repository + | ||||
| 		"/deploy?sap-client=" + config.Client | ||||
| 	reqBody := deployRequestBody | ||||
| 	jsonBody, marshalErr := json.Marshal(reqBody) | ||||
| 	if marshalErr != nil { | ||||
| 		return errors.Wrapf(marshalErr, "Deploying repository to abap system failed json body marshalling") | ||||
| 	} | ||||
| 	header := make(http.Header) | ||||
| 	header.Set("Content-Type", "application/json") | ||||
| 	header.Add("Accept", "application/json") | ||||
| 	resp, httpErr := httpClient.SendRequest("POST", requestURL, bytes.NewBuffer(jsonBody), header, nil) | ||||
| 	defer func() { | ||||
| 		if resp != nil && resp.Body != nil { | ||||
| 			resp.Body.Close() | ||||
| 		} | ||||
| 	}() | ||||
| 	if httpErr != nil { | ||||
| 		_, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) | ||||
| 		if errorDumpParseErr != nil { | ||||
| 			return errorDumpParseErr | ||||
| 		} | ||||
| 		log.Entry().Error("Failed During Deploy to Abap system") | ||||
| 		return httpErr | ||||
| 	} | ||||
| 	parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &response) | ||||
| 	if parsingErr != nil { | ||||
| 		return parsingErr | ||||
| 	} | ||||
| 	log.Entry().Infof("Response for deploy command : %v", response.Result) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Uses the repository details to check if the repository already exists in the system or not | ||||
| func getRepository(config *gctsDeployOptions, httpClient piperhttp.Sender) (*getRepositoryResponseBody, error) { | ||||
| 	var response getRepositoryResponseBody | ||||
| 	requestURL := config.Host + | ||||
| 		"/sap/bc/cts_abapvcs/repository/" + config.Repository + | ||||
| 		"?sap-client=" + config.Client | ||||
|  | ||||
| 	resp, httpErr := httpClient.SendRequest("GET", requestURL, nil, nil, nil) | ||||
| 	defer func() { | ||||
| 		if resp != nil && resp.Body != nil { | ||||
| 			resp.Body.Close() | ||||
| 		} | ||||
| 	}() | ||||
| 	if httpErr != nil { | ||||
| 		_, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) | ||||
| 		if errorDumpParseErr != nil { | ||||
| 			return nil, errorDumpParseErr | ||||
| 		} | ||||
| 		log.Entry().Infof("Error while repository Check : %v", httpErr) | ||||
| 		return &response, httpErr | ||||
| 	} else if resp == nil { | ||||
| 		return &response, errors.New("did not retrieve a HTTP response") | ||||
| 	} | ||||
|  | ||||
| 	parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &response) | ||||
| 	if parsingErr != nil { | ||||
| 		return &response, parsingErr | ||||
| 	} | ||||
| 	return &response, nil | ||||
| } | ||||
|  | ||||
| // Function to delete configuration key for repositories | ||||
| func deleteConfigKey(deployConfig *gctsDeployOptions, httpClient piperhttp.Sender, configToDelete string) error { | ||||
| 	log.Entry().Infof("gCTS Deploy : Delete configuration key %v", configToDelete) | ||||
| 	requestURL := deployConfig.Host + | ||||
| 		"/sap/bc/cts_abapvcs/repository/" + deployConfig.Repository + | ||||
| 		"/config/" + configToDelete + "?sap-client=" + deployConfig.Client | ||||
| 	header := make(http.Header) | ||||
| 	header.Set("Content-Type", "application/json") | ||||
| 	header.Add("Accept", "application/json") | ||||
| 	resp, httpErr := httpClient.SendRequest("DELETE", requestURL, nil, header, nil) | ||||
| 	defer func() { | ||||
| 		if resp != nil && resp.Body != nil { | ||||
| 			resp.Body.Close() | ||||
| 		} | ||||
| 	}() | ||||
| 	if httpErr != nil { | ||||
| 		_, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) | ||||
| 		if errorDumpParseErr != nil { | ||||
| 			return errorDumpParseErr | ||||
| 		} | ||||
| 		log.Entry().Error("Failure during deletion of configuration value") | ||||
| 		return httpErr | ||||
| 	} | ||||
| 	log.Entry().Infof("gCTS Deploy : Delete configuration key %v successful", configToDelete) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Function to set configuration key for repositories | ||||
| func setConfigKey(deployConfig *gctsDeployOptions, httpClient piperhttp.Sender, configToSet *setConfigKeyBody) error { | ||||
| 	log.Entry().Infof("gCTS Deploy : Start of set configuration key %v and value %v", configToSet.Key, configToSet.Value) | ||||
| 	requestURL := deployConfig.Host + | ||||
| 		"/sap/bc/cts_abapvcs/repository/" + deployConfig.Repository + | ||||
| 		"/config?sap-client=" + deployConfig.Client | ||||
|  | ||||
| 	reqBody := configToSet | ||||
| 	jsonBody, marshalErr := json.Marshal(reqBody) | ||||
| 	if marshalErr != nil { | ||||
| 		return errors.Wrapf(marshalErr, "Setting config key: %v and value: %v on the ABAP system %v failed", configToSet.Key, configToSet.Value, deployConfig.Host) | ||||
| 	} | ||||
| 	header := make(http.Header) | ||||
| 	header.Set("Content-Type", "application/json") | ||||
| 	header.Add("Accept", "application/json") | ||||
| 	resp, httpErr := httpClient.SendRequest("POST", requestURL, bytes.NewBuffer(jsonBody), header, nil) | ||||
| 	defer func() { | ||||
| 		if resp != nil && resp.Body != nil { | ||||
| 			resp.Body.Close() | ||||
| 		} | ||||
| 	}() | ||||
| 	if httpErr != nil { | ||||
| 		_, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) | ||||
| 		if errorDumpParseErr != nil { | ||||
| 			return errorDumpParseErr | ||||
| 		} | ||||
| 		log.Entry().Error("Failure during setting configuration value") | ||||
| 		return httpErr | ||||
| 	} | ||||
| 	log.Entry(). | ||||
| 		WithField("repository", deployConfig.Repository). | ||||
| 		Infof("successfully set configuration value key %v and value %v", configToSet.Key, configToSet.Value) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func pullByCommit(config *gctsDeployOptions, telemetryData *telemetry.CustomData, command command.ExecRunner, httpClient piperhttp.Sender) error { | ||||
|  | ||||
| 	cookieJar, cookieErr := cookiejar.New(nil) | ||||
| 	if cookieErr != nil { | ||||
| @@ -41,7 +476,7 @@ func deployCommit(config *gctsDeployOptions, telemetryData *telemetry.CustomData | ||||
|  | ||||
| 	requestURL := config.Host + | ||||
| 		"/sap/bc/cts_abapvcs/repository/" + config.Repository + | ||||
| 		"/pullByCommit?sap-client=" + config.Client | ||||
| 		"/pullByCommit?sap-client=" + config.Client + "&request=" + config.Commit | ||||
|  | ||||
| 	if config.Commit != "" { | ||||
| 		log.Entry().Infof("preparing to deploy specified commit %v", config.Commit) | ||||
| @@ -59,6 +494,10 @@ func deployCommit(config *gctsDeployOptions, telemetryData *telemetry.CustomData | ||||
| 	}() | ||||
|  | ||||
| 	if httpErr != nil { | ||||
| 		_, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) | ||||
| 		if errorDumpParseErr != nil { | ||||
| 			return errorDumpParseErr | ||||
| 		} | ||||
| 		return httpErr | ||||
| 	} else if resp == nil { | ||||
| 		return errors.New("did not retrieve a HTTP response") | ||||
| @@ -81,3 +520,287 @@ func deployCommit(config *gctsDeployOptions, telemetryData *telemetry.CustomData | ||||
| 		Infof("successfully deployed commit %v (previous commit was %v)", response.Path("toCommit").Data().(string), response.Path("fromCommit").Data().(string)) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func createRepositoryForDeploy(config *gctsCreateRepositoryOptions, telemetryData *telemetry.CustomData, command command.ExecRunner, httpClient piperhttp.Sender, repositoryConfig []repositoryConfiguration) error { | ||||
|  | ||||
| 	cookieJar, cookieErr := cookiejar.New(nil) | ||||
| 	if cookieErr != nil { | ||||
| 		return errors.Wrapf(cookieErr, "creating repository on the ABAP system %v failed", config.Host) | ||||
| 	} | ||||
| 	clientOptions := piperhttp.ClientOptions{ | ||||
| 		CookieJar: cookieJar, | ||||
| 		Username:  config.Username, | ||||
| 		Password:  config.Password, | ||||
| 	} | ||||
| 	httpClient.SetOptions(clientOptions) | ||||
|  | ||||
| 	type repoData struct { | ||||
| 		RID                 string                    `json:"rid"` | ||||
| 		Name                string                    `json:"name"` | ||||
| 		Role                string                    `json:"role"` | ||||
| 		Type                string                    `json:"type"` | ||||
| 		VSID                string                    `json:"vsid"` | ||||
| 		RemoteRepositoryURL string                    `json:"url"` | ||||
| 		Config              []repositoryConfiguration `json:"config"` | ||||
| 	} | ||||
|  | ||||
| 	type createRequestBody struct { | ||||
| 		Repository string   `json:"repository"` | ||||
| 		Data       repoData `json:"data"` | ||||
| 	} | ||||
|  | ||||
| 	reqBody := createRequestBody{ | ||||
| 		Repository: config.Repository, | ||||
| 		Data: repoData{ | ||||
| 			RID:                 config.Repository, | ||||
| 			Name:                config.Repository, | ||||
| 			Role:                config.Role, | ||||
| 			Type:                config.Type, | ||||
| 			VSID:                config.VSID, | ||||
| 			RemoteRepositoryURL: config.RemoteRepositoryURL, | ||||
| 			Config:              repositoryConfig, | ||||
| 		}, | ||||
| 	} | ||||
| 	jsonBody, marshalErr := json.Marshal(reqBody) | ||||
|  | ||||
| 	if marshalErr != nil { | ||||
| 		return errors.Wrapf(marshalErr, "creating repository on the ABAP system %v failed", config.Host) | ||||
| 	} | ||||
|  | ||||
| 	header := make(http.Header) | ||||
| 	header.Set("Content-Type", "application/json") | ||||
| 	header.Add("Accept", "application/json") | ||||
|  | ||||
| 	url := config.Host + "/sap/bc/cts_abapvcs/repository?sap-client=" + config.Client | ||||
|  | ||||
| 	resp, httpErr := httpClient.SendRequest("POST", url, bytes.NewBuffer(jsonBody), header, nil) | ||||
|  | ||||
| 	defer func() { | ||||
| 		if resp != nil && resp.Body != nil { | ||||
| 			resp.Body.Close() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	if resp == nil { | ||||
| 		return errors.Errorf("creating repository on the ABAP system %v failed: %v", config.Host, httpErr) | ||||
| 	} | ||||
|  | ||||
| 	if httpErr != nil { | ||||
| 		response, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) | ||||
| 		if errorDumpParseErr != nil { | ||||
| 			return errorDumpParseErr | ||||
| 		} | ||||
| 		if resp.StatusCode == 500 { | ||||
| 			if response.Exception == "Repository already exists" { | ||||
| 				log.Entry(). | ||||
| 					WithField("repository", config.Repository). | ||||
| 					Infof("the repository already exists on the ABAP system %v", config.Host) | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 		log.Entry().Errorf("a HTTP error occurred! Response body: %v", response) | ||||
| 		return errors.Wrapf(httpErr, "creating repository on the ABAP system %v failed", config.Host) | ||||
| 	} | ||||
|  | ||||
| 	log.Entry(). | ||||
| 		WithField("repository", config.Repository). | ||||
| 		Infof("successfully created the repository on ABAP system %v", config.Host) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func getConfigurationMetadata(config *gctsDeployOptions, httpClient piperhttp.Sender) (*configurationMetadataBody, error) { | ||||
| 	var response configurationMetadataBody | ||||
| 	log.Entry().Infof("Starting to retrieve configuration metadata from the system") | ||||
| 	requestURL := config.Host + | ||||
| 		"/sap/bc/cts_abapvcs/config" | ||||
|  | ||||
| 	resp, httpErr := httpClient.SendRequest("GET", requestURL, nil, nil, nil) | ||||
| 	defer func() { | ||||
| 		if resp != nil && resp.Body != nil { | ||||
| 			resp.Body.Close() | ||||
| 		} | ||||
| 	}() | ||||
| 	if httpErr != nil { | ||||
| 		_, errorDumpParseErr := parseErrorDumpFromResponseBody(resp) | ||||
| 		if errorDumpParseErr != nil { | ||||
| 			return nil, errorDumpParseErr | ||||
| 		} | ||||
| 		log.Entry().Infof("Error while repository Check : %v", httpErr) | ||||
| 		return &response, httpErr | ||||
| 	} else if resp == nil { | ||||
| 		return &response, errors.New("did not retrieve a HTTP response") | ||||
| 	} | ||||
|  | ||||
| 	parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &response) | ||||
| 	if parsingErr != nil { | ||||
| 		return &response, parsingErr | ||||
| 	} | ||||
| 	log.Entry().Infof("System Available for further step processing. The configuration metadata was successfully retrieved.") | ||||
| 	return &response, nil | ||||
| } | ||||
|  | ||||
| func splitConfigurationToMap(inputConfigMap map[string]interface{}, configMetadataInSystem configurationMetadataBody) ([]repositoryConfiguration, error) { | ||||
| 	log.Entry().Infof("Parsing the configurations from the yml file") | ||||
| 	var configurations []repositoryConfiguration | ||||
| 	for key, value := range inputConfigMap { | ||||
| 		foundConfigMetadata, _ := findConfigurationMetadata(key, configMetadataInSystem) | ||||
| 		configValue := fmt.Sprint(value) | ||||
| 		if (configMetadata{}) != foundConfigMetadata { | ||||
| 			if foundConfigMetadata.Datatype == "BOOLEAN" && foundConfigMetadata.Example == "X" { | ||||
| 				if configValue == "false" || configValue == "" { | ||||
| 					configValue = "" | ||||
| 				} else if configValue == "true" || configValue == "X" { | ||||
| 					configValue = "X" | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		configuration := repositoryConfiguration{ | ||||
| 			Key:   key, | ||||
| 			Value: configValue, | ||||
| 		} | ||||
| 		configurations = append(configurations, configuration) | ||||
|  | ||||
| 	} | ||||
| 	log.Entry().Infof("The Configurations for the repoistory creation are : %v", configurations) | ||||
| 	return configurations, nil | ||||
| } | ||||
|  | ||||
| func findConfigurationMetadata(configToFind string, configurationsAvailable configurationMetadataBody) (configMetadata, error) { | ||||
| 	var configStruct configMetadata | ||||
| 	for _, config := range configurationsAvailable.Config { | ||||
| 		if config.Ckey == configToFind { | ||||
| 			return config, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return configStruct, nil | ||||
| } | ||||
|  | ||||
| // Error handling for failure responses from the gcts api calls | ||||
| func parseErrorDumpFromResponseBody(responseBody *http.Response) (*errorLogBody, error) { | ||||
| 	var errorDump errorLogBody | ||||
| 	parsingErr := piperhttp.ParseHTTPResponseBodyJSON(responseBody, &errorDump) | ||||
| 	if parsingErr != nil { | ||||
| 		return &errorDump, parsingErr | ||||
| 	} | ||||
| 	for _, errorLogData := range errorDump.ErrorLog { | ||||
| 		log.Entry().Errorf("Time: %v, User: %v, Section: %v, Action: %v, Severity: %v, Message: %v", | ||||
| 			errorLogData.Time, errorLogData.User, errorLogData.Section, | ||||
| 			errorLogData.Action, errorLogData.Severity, errorLogData.Message) | ||||
| 		for _, protocolErrorData := range errorLogData.Protocol { | ||||
| 			log.Entry().Errorf("Type: %v", protocolErrorData.Type) | ||||
| 			for _, protocols := range protocolErrorData.Protocol { | ||||
| 				if strings.Contains(protocols, "4 ETW000 ") { | ||||
| 					protocols = strings.ReplaceAll(protocols, "4 ETW000 ", "") | ||||
| 				} else if strings.Contains(protocols, "4EETW000 ") { | ||||
| 					protocols = strings.ReplaceAll(protocols, "4EETW000 ", "ERROR: ") | ||||
| 				} | ||||
| 				log.Entry().Error(protocols) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return &errorDump, nil | ||||
| } | ||||
|  | ||||
| type repositoryConfiguration struct { | ||||
| 	Key   string `json:"key"` | ||||
| 	Value string `json:"value"` | ||||
| } | ||||
|  | ||||
| type getRepositoryResponseBody struct { | ||||
| 	Result struct { | ||||
| 		Rid    string `json:"rid"` | ||||
| 		Name   string `json:"name"` | ||||
| 		Role   string `json:"role"` | ||||
| 		Vsid   string `json:"vsid"` | ||||
| 		Status string `json:"status"` | ||||
| 		Branch string `json:"branch"` | ||||
| 		Url    string `json:"url"` | ||||
| 		Config []struct { | ||||
| 			Key      string `json:"key"` | ||||
| 			Value    string `json:"value"` | ||||
| 			Category string `json:"category"` | ||||
| 		} `json:"config"` | ||||
| 		Objects       int64  `json:"objects"` | ||||
| 		CurrentCommit string `json:"currentCommit"` | ||||
| 		Connection    string `json:"connection"` | ||||
| 	} `json:"result"` | ||||
| } | ||||
|  | ||||
| type setConfigKeyBody struct { | ||||
| 	Key   string `json:"key"` | ||||
| 	Value string `json:"value"` | ||||
| } | ||||
|  | ||||
| type switchBranchResponseBody struct { | ||||
| 	Result struct { | ||||
| 		FromCommit string `json:"fromCommit"` | ||||
| 		ToCommit   string `json:"ToCommit"` | ||||
| 	} `json:"result"` | ||||
| 	Log []struct { | ||||
| 		Time     string `json:"time"` | ||||
| 		User     string `json:"user"` | ||||
| 		Section  string `json:"section"` | ||||
| 		Action   string `json:"Action"` | ||||
| 		Severity string `json:"Severity"` | ||||
| 		Message  string `json:"Message"` | ||||
| 	} `json:"log"` | ||||
| } | ||||
|  | ||||
| type deployCommitToAbapSystemBody struct { | ||||
| 	Repository string `json:"repository"` | ||||
| 	Scope      string `json:"scope"` | ||||
| 	Commit     string `json:"commit"` | ||||
| 	Objects    []struct { | ||||
| 		Object string `json:"object"` | ||||
| 		Type   string `json:"type"` | ||||
| 		User   string `json:"user"` | ||||
| 		Pgmid  string `json:"pgmid"` | ||||
| 		Keys   []struct { | ||||
| 			Tabname string `json:"tabname"` | ||||
| 			Columns []struct { | ||||
| 				Key     string `json:"key"` | ||||
| 				Field   string `json:"field"` | ||||
| 				Value   string `json:"value"` | ||||
| 				Type    string `json:"type"` | ||||
| 				Inttype string `json:"inttype"` | ||||
| 				Length  string `json:"length"` | ||||
| 			} | ||||
| 		} | ||||
| 	} `json:"objects"` | ||||
| } | ||||
|  | ||||
| type configMetadata struct { | ||||
| 	Ckey         string `json:"ckey"` | ||||
| 	Ctype        string `json:"ctype"` | ||||
| 	Cvisible     string `json:"cvisible"` | ||||
| 	Datatype     string `json:"datatype"` | ||||
| 	DefaultValue string `json:"defaultValue"` | ||||
| 	Description  string `json:"description"` | ||||
| 	Category     string `json:"category"` | ||||
| 	UiElement    string `json:"uiElement"` | ||||
| 	Example      string `json:"example"` | ||||
| } | ||||
|  | ||||
| type configurationMetadataBody struct { | ||||
| 	Config []configMetadata `json:"config"` | ||||
| } | ||||
|  | ||||
| type errorProtocolbody struct { | ||||
| 	Type     string   `json:"type"` | ||||
| 	Protocol []string `json:"protocol"` | ||||
| } | ||||
|  | ||||
| type errorLog struct { | ||||
| 	Time     int                 `json:"time"` | ||||
| 	User     string              `json:"user"` | ||||
| 	Section  string              `json:"section"` | ||||
| 	Action   string              `json:"action"` | ||||
| 	Severity string              `json:"severity"` | ||||
| 	Message  string              `json:"message"` | ||||
| 	Protocol []errorProtocolbody `json:"protocol"` | ||||
| } | ||||
|  | ||||
| type errorLogBody struct { | ||||
| 	ErrorLog  []errorLog `json:"errorLog"` | ||||
| 	Exception string     `json:"exception"` | ||||
| } | ||||
|   | ||||
| @@ -14,15 +14,23 @@ import ( | ||||
| ) | ||||
|  | ||||
| type gctsDeployOptions struct { | ||||
| 	Username   string `json:"username,omitempty"` | ||||
| 	Password   string `json:"password,omitempty"` | ||||
| 	Repository string `json:"repository,omitempty"` | ||||
| 	Host       string `json:"host,omitempty"` | ||||
| 	Client     string `json:"client,omitempty"` | ||||
| 	Commit     string `json:"commit,omitempty"` | ||||
| 	Username            string                 `json:"username,omitempty"` | ||||
| 	Password            string                 `json:"password,omitempty"` | ||||
| 	Repository          string                 `json:"repository,omitempty"` | ||||
| 	Host                string                 `json:"host,omitempty"` | ||||
| 	Client              string                 `json:"client,omitempty"` | ||||
| 	Commit              string                 `json:"commit,omitempty"` | ||||
| 	RemoteRepositoryURL string                 `json:"remoteRepositoryURL,omitempty"` | ||||
| 	Role                string                 `json:"role,omitempty"` | ||||
| 	VSID                string                 `json:"vSID,omitempty"` | ||||
| 	Type                string                 `json:"type,omitempty"` | ||||
| 	Branch              string                 `json:"branch,omitempty"` | ||||
| 	Scope               string                 `json:"scope,omitempty"` | ||||
| 	Rollback            bool                   `json:"rollback,omitempty"` | ||||
| 	Configuration       map[string]interface{} `json:"configuration,omitempty"` | ||||
| } | ||||
|  | ||||
| // GctsDeployCommand Pulls a commit from the remote Git repository to a local repository | ||||
| // GctsDeployCommand Deploys a Git Repository to a local Repository and then to an ABAP System | ||||
| func GctsDeployCommand() *cobra.Command { | ||||
| 	const STEP_NAME = "gctsDeploy" | ||||
|  | ||||
| @@ -32,8 +40,9 @@ func GctsDeployCommand() *cobra.Command { | ||||
|  | ||||
| 	var createGctsDeployCmd = &cobra.Command{ | ||||
| 		Use:   STEP_NAME, | ||||
| 		Short: "Pulls a commit from the remote Git repository to a local repository", | ||||
| 		Long:  `Pulls a commit from the corresponding remote Git repository to a specified local repository on an ABAP system. If no <commit> parameter is specified, this step will pull the latest commit available on the remote repository.`, | ||||
| 		Short: "Deploys a Git Repository to a local Repository and then to an ABAP System", | ||||
| 		Long: `The steps deploys a git repository to an ABAP System. If a repository does not exists in the system, it creates and clones it to the local | ||||
| repository and then deploys it into the ABAP system.`, | ||||
| 		PreRunE: func(cmd *cobra.Command, _ []string) error { | ||||
| 			startTime = time.Now() | ||||
| 			log.SetStepName(STEP_NAME) | ||||
| @@ -87,12 +96,20 @@ func addGctsDeployFlags(cmd *cobra.Command, stepConfig *gctsDeployOptions) { | ||||
| 	cmd.Flags().StringVar(&stepConfig.Host, "host", os.Getenv("PIPER_host"), "Specifies the protocol and host address, including the port. Please provide in the format `<protocol>://<host>:<port>`. Supported protocols are `http` and `https`.") | ||||
| 	cmd.Flags().StringVar(&stepConfig.Client, "client", os.Getenv("PIPER_client"), "Specifies the client of the ABAP system to be addressed") | ||||
| 	cmd.Flags().StringVar(&stepConfig.Commit, "commit", os.Getenv("PIPER_commit"), "Specifies the commit to be deployed") | ||||
| 	cmd.Flags().StringVar(&stepConfig.RemoteRepositoryURL, "remoteRepositoryURL", os.Getenv("PIPER_remoteRepositoryURL"), "URL of the corresponding remote repository") | ||||
| 	cmd.Flags().StringVar(&stepConfig.Role, "role", os.Getenv("PIPER_role"), "Role of the local repository. Choose between 'TARGET' and 'SOURCE'. Local repositories with a TARGET role will NOT be able to be the source of code changes") | ||||
| 	cmd.Flags().StringVar(&stepConfig.VSID, "vSID", os.Getenv("PIPER_vSID"), "Virtual SID of the local repository. The vSID corresponds to the transport route that delivers content to the remote Git repository") | ||||
| 	cmd.Flags().StringVar(&stepConfig.Type, "type", `GIT`, "Type of the used source code management tool") | ||||
| 	cmd.Flags().StringVar(&stepConfig.Branch, "branch", os.Getenv("PIPER_branch"), "Name of the branch to which the deploy has to be done to.") | ||||
| 	cmd.Flags().StringVar(&stepConfig.Scope, "scope", os.Getenv("PIPER_scope"), "The scope of the gcts deploy api call") | ||||
| 	cmd.Flags().BoolVar(&stepConfig.Rollback, "rollback", false, "The rollback flag for a failure during the deploy step. A true value would mean gCTS would roll back to the last clean state") | ||||
|  | ||||
| 	cmd.MarkFlagRequired("username") | ||||
| 	cmd.MarkFlagRequired("password") | ||||
| 	cmd.MarkFlagRequired("repository") | ||||
| 	cmd.MarkFlagRequired("host") | ||||
| 	cmd.MarkFlagRequired("client") | ||||
| 	cmd.MarkFlagRequired("remoteRepositoryURL") | ||||
| } | ||||
|  | ||||
| // retrieve step metadata | ||||
| @@ -101,7 +118,7 @@ func gctsDeployMetadata() config.StepData { | ||||
| 		Metadata: config.StepMetadata{ | ||||
| 			Name:        "gctsDeploy", | ||||
| 			Aliases:     []config.Alias{}, | ||||
| 			Description: "Pulls a commit from the remote Git repository to a local repository", | ||||
| 			Description: "Deploys a Git Repository to a local Repository and then to an ABAP System", | ||||
| 		}, | ||||
| 		Spec: config.StepSpec{ | ||||
| 			Inputs: config.StepInputs{ | ||||
| @@ -166,6 +183,70 @@ func gctsDeployMetadata() config.StepData { | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "remoteRepositoryURL", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   true, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "role", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "vSID", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "type", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "branch", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "scope", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "rollback", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "bool", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "configuration", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"}, | ||||
| 						Type:        "map[string]interface{}", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{{Name: "gctsRepositoryConfigurations"}}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import ( | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestGctsDeploySuccess(t *testing.T) { | ||||
| func TestGctsPullByCommitSuccess(t *testing.T) { | ||||
|  | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| @@ -35,12 +35,12 @@ func TestGctsDeploySuccess(t *testing.T) { | ||||
| 			] | ||||
| 		}`} | ||||
|  | ||||
| 		err := deployCommit(&config, nil, &httpClient) | ||||
| 		err := pullByCommit(&config, nil, nil, &httpClient) | ||||
|  | ||||
| 		if assert.NoError(t, err) { | ||||
|  | ||||
| 			t.Run("check url", func(t *testing.T) { | ||||
| 				assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/pullByCommit?sap-client=000", httpClient.URL) | ||||
| 				assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/pullByCommit?sap-client=000&request=", httpClient.URL) | ||||
| 			}) | ||||
|  | ||||
| 			t.Run("check method", func(t *testing.T) { | ||||
| @@ -60,7 +60,7 @@ func TestGctsDeploySuccess(t *testing.T) { | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestGctsDeployFailure(t *testing.T) { | ||||
| func TestGctsPullByCommitFailure(t *testing.T) { | ||||
|  | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| @@ -73,39 +73,617 @@ func TestGctsDeployFailure(t *testing.T) { | ||||
| 	t.Run("http error occurred", func(t *testing.T) { | ||||
|  | ||||
| 		httpClient := httpMockGcts{StatusCode: 500, ResponseBody: `{ | ||||
| 			"log": [ | ||||
| 				{ | ||||
| 					"time": 20180606130524, | ||||
| 					"user": "JENKINS", | ||||
| 					"section": "REPOSITORY_FACTORY", | ||||
| 					"action": "CREATE_REPOSITORY", | ||||
| 					"severity": "INFO", | ||||
| 					"message": "Start action CREATE_REPOSITORY review", | ||||
| 					"code": "GCTS.API.410" | ||||
| 				} | ||||
| 			], | ||||
| 			"errorLog": [ | ||||
| 				{ | ||||
| 					"time": 20180606130524, | ||||
| 					"user": "JENKINS", | ||||
| 					"section": "REPOSITORY_FACTORY", | ||||
| 					"action": "CREATE_REPOSITORY", | ||||
| 					"severity": "INFO", | ||||
| 					"message": "Start action CREATE_REPOSITORY review", | ||||
| 					"code": "GCTS.API.410" | ||||
| 				} | ||||
| 			], | ||||
| 			"exception": { | ||||
| 				"message": "repository_not_found", | ||||
| 				"description": "Repository not found", | ||||
| 				"code": 404 | ||||
| 			} | ||||
| 		}`} | ||||
| 			"exception": "No relation between system and repository" | ||||
| 		    }`} | ||||
|  | ||||
| 		err := deployCommit(&config, nil, &httpClient) | ||||
| 		err := pullByCommit(&config, nil, nil, &httpClient) | ||||
|  | ||||
| 		assert.EqualError(t, err, "a http error occurred") | ||||
|  | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestGctsGetRepositorySuccess(t *testing.T) { | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| 		Client:     "000", | ||||
| 		Repository: "testRepo", | ||||
| 		Username:   "testUser", | ||||
| 		Password:   "testPassword", | ||||
| 	} | ||||
| 	t.Run("Get Repository Success Test", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Repository == "testRepo" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 200, ResponseBody: `{ | ||||
| 				"result": { | ||||
| 				    "rid": "testrepo", | ||||
| 				    "name": "testRepo", | ||||
| 				    "role": "SOURCE", | ||||
| 				    "type": "GIT", | ||||
| 				    "vsid": "GIT", | ||||
| 				    "status": "READY", | ||||
| 				    "branch": "dummy_branch", | ||||
| 				    "url": "https://example.git.com/testRepo", | ||||
| 				    "createdBy": "testUser", | ||||
| 				    "createdDate": "dummy_date", | ||||
| 				    "config": [ | ||||
| 					{ | ||||
| 					    "key": "CURRENT_COMMIT", | ||||
| 					    "value": "dummy_commit_number", | ||||
| 					    "category": "GENERAL", | ||||
| 					    "scope": "local" | ||||
| 					} | ||||
| 				    ], | ||||
| 				    "objects": 1, | ||||
| 				    "currentCommit": "dummy_commit_number", | ||||
| 				    "connection": "ssl" | ||||
| 				} | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		repository, err := getRepository(&config, &httpClient) | ||||
|  | ||||
| 		if assert.NoError(t, err) { | ||||
| 			t.Run("check url", func(t *testing.T) { | ||||
| 				assert.Equal(t, "https://example.git.com/testRepo", repository.Result.Url) | ||||
| 			}) | ||||
| 			t.Run("check rid", func(t *testing.T) { | ||||
| 				assert.Equal(t, "testrepo", repository.Result.Rid) | ||||
| 			}) | ||||
| 			t.Run("check commit id", func(t *testing.T) { | ||||
| 				assert.Equal(t, "dummy_commit_number", repository.Result.CurrentCommit) | ||||
| 			}) | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestGctsGetRepositoryFailure(t *testing.T) { | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| 		Client:     "000", | ||||
| 		Repository: "testRepoNotExists", | ||||
| 		Username:   "testUser", | ||||
| 		Password:   "testPassword", | ||||
| 	} | ||||
| 	t.Run("Get Repository Success Test", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Repository == "testRepoNotExists" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 500, ResponseBody: `{ | ||||
| 				"exception": "No relation between system and repository" | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		_, err := getRepository(&config, &httpClient) | ||||
|  | ||||
| 		assert.EqualError(t, err, "a http error occurred") | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestGctsSwitchBranchSuccess(t *testing.T) { | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| 		Client:     "000", | ||||
| 		Repository: "testRepo", | ||||
| 		Branch:     "dummyBranch", | ||||
| 		Username:   "testUser", | ||||
| 		Password:   "testPassword", | ||||
| 	} | ||||
|  | ||||
| 	t.Run("Switch Branch success", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Branch == "dummyBranch" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 200, ResponseBody: `{ | ||||
| 				"result": { | ||||
| 				    "rid": "testrepo", | ||||
| 				    "checkoutTime": 20210413082242, | ||||
| 				    "fromCommit": "from_dummy_commit", | ||||
| 				    "toCommit": "to_dummy_commit", | ||||
| 				    "caller": "testUser", | ||||
| 				    "request": "GITKUKDUMMY", | ||||
| 				    "type": "BRANCH_SW", | ||||
| 				    "state": "DONE", | ||||
| 				    "rc": "0000" | ||||
| 				} | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		responseBody, err := switchBranch(&config, &httpClient, "dummyCurrentBranch", "dummyTargetBranch") | ||||
|  | ||||
| 		if assert.NoError(t, err) { | ||||
| 			t.Run("check from commit", func(t *testing.T) { | ||||
| 				assert.Equal(t, "from_dummy_commit", responseBody.Result.FromCommit) | ||||
| 			}) | ||||
| 			t.Run("check to commit", func(t *testing.T) { | ||||
| 				assert.Equal(t, "to_dummy_commit", responseBody.Result.ToCommit) | ||||
| 			}) | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestGctsSwitchBranchFailure(t *testing.T) { | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| 		Client:     "000", | ||||
| 		Repository: "testRepo", | ||||
| 		Branch:     "dummyBranchNotExists", | ||||
| 		Username:   "testUser", | ||||
| 		Password:   "testPassword", | ||||
| 	} | ||||
| 	t.Run("Switch Branch failure Test", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Branch == "dummyBranchNotExists" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 500, ResponseBody: `{ | ||||
| 				"errorLog": [ | ||||
| 				    { | ||||
| 					"time": 20210414102742, | ||||
| 					"severity": "ERROR", | ||||
| 					"message": "The branch to switch to - 'feature1' - does not exist", | ||||
| 					"code": "GCTS.CLIENT.1320" | ||||
| 				    } | ||||
| 				], | ||||
| 				"log": [ | ||||
| 				    { | ||||
| 					"time": 20210414102742, | ||||
| 					"user": "testUser", | ||||
| 					"section": "REPOSITORY", | ||||
| 					"action": "SWITCH_BRANCH", | ||||
| 					"severity": "ERROR", | ||||
| 					"message": "20210414102742: Error action SWITCH_BRANCH 20210414_102740_B4EC329722B5C611B35B345F3B5F8FAA" | ||||
| 				    }, | ||||
| 				    { | ||||
| 					"time": 20210414102742, | ||||
| 					"user": "testUser", | ||||
| 					"section": "REPOSITORY", | ||||
| 					"action": "SWITCH_BRANCH", | ||||
| 					"severity": "ERROR", | ||||
| 					"message": "20210414102742: Error action SWITCH_BRANCH Client error" | ||||
| 				    } | ||||
| 				], | ||||
| 				"exception": "Cannot switch branch of local repository to selected branch." | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		_, err := getRepository(&config, &httpClient) | ||||
|  | ||||
| 		assert.EqualError(t, err, "a http error occurred") | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestCreateRepositorySuccess(t *testing.T) { | ||||
| 	config := gctsCreateRepositoryOptions{ | ||||
| 		Host:                "http://testHost.com:50000", | ||||
| 		Client:              "000", | ||||
| 		Repository:          "testRepo", | ||||
| 		Username:            "testUser", | ||||
| 		Password:            "testPassword", | ||||
| 		RemoteRepositoryURL: "http://testRepoUrl.com", | ||||
| 		Role:                "dummyRole", | ||||
| 		VSID:                "dummyVsid", | ||||
| 		Type:                "dummyType", | ||||
| 	} | ||||
| 	t.Run("Create Repository Success", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Repository == "testRepo" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 200, ResponseBody: `{ | ||||
| 				"repository": { | ||||
| 				    "rid": "testrepo", | ||||
| 				    "name": "testRepo", | ||||
| 				    "role": "dummyRole", | ||||
| 				    "type": "dummyType", | ||||
| 				    "vsid": "dummyVsid", | ||||
| 				    "status": "CREATED", | ||||
| 				    "branch": "dummyBranch", | ||||
| 				    "url": "http://testRepoUrl.com", | ||||
| 				    "createdBy": "testUser", | ||||
| 				    "createdDate": "2021-04-14", | ||||
| 				    "config": [ | ||||
| 					{ | ||||
| 					    "key": "CLIENT_VCS_CONNTYPE", | ||||
| 					    "value": "ssl", | ||||
| 					    "category": "CONNECTION", | ||||
| 					    "scope": "local" | ||||
| 					}, | ||||
| 					{ | ||||
| 					    "key": "CLIENT_VCS_URI", | ||||
| 					    "value": "http://testRepoUrl.com", | ||||
| 					    "category": "CONNECTION", | ||||
| 					    "scope": "local" | ||||
| 					} | ||||
| 				    ], | ||||
| 				    "connection": "ssl" | ||||
| 				} | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		err := createRepositoryForDeploy(&config, nil, nil, &httpClient, nil) | ||||
| 		assert.NoError(t, err) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestCreateRepositoryFailure(t *testing.T) { | ||||
| 	config := gctsCreateRepositoryOptions{ | ||||
| 		Host:                "http://testHost.com:50000", | ||||
| 		Client:              "000", | ||||
| 		Repository:          "testRepoExists", | ||||
| 		Username:            "testUser", | ||||
| 		Password:            "testPassword", | ||||
| 		RemoteRepositoryURL: "http://testRepoUrlFail.com", | ||||
| 		Role:                "dummyRole", | ||||
| 		VSID:                "dummyVsid", | ||||
| 		Type:                "dummyType", | ||||
| 	} | ||||
| 	t.Run("Create Repository Failure", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Repository == "testRepoExists" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 500, ResponseBody: `{ | ||||
| 				"errorLog": [ | ||||
| 				    { | ||||
| 					"time": 20210506153611, | ||||
| 					"user": "testUser", | ||||
| 					"section": "SYSTEM", | ||||
| 					"action": "CREATE_REPOSITORY", | ||||
| 					"severity": "ERROR", | ||||
| 					"message": "20210506153611: Error action CREATE_REPOSITORY Repository already exists" | ||||
| 				    } | ||||
| 				], | ||||
| 				"log": [ | ||||
| 				    { | ||||
| 					"time": 20210506153611, | ||||
| 					"user": "testUser", | ||||
| 					"section": "SYSTEM", | ||||
| 					"action": "CREATE_REPOSITORY", | ||||
| 					"severity": "ERROR", | ||||
| 					"message": "20210506153611: Error action CREATE_REPOSITORY Repository already exists" | ||||
| 				    } | ||||
| 				], | ||||
| 				"exception": "Some Error" | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		err := createRepositoryForDeploy(&config, nil, nil, &httpClient, nil) | ||||
| 		assert.EqualError(t, err, "creating repository on the ABAP system http://testHost.com:50000 failed: a http error occurred") | ||||
| 	}) | ||||
| 	t.Run("Create Repository Failure", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Repository == "testRepoExists" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 500, ResponseBody: `{ | ||||
| 				"errorLog": [ | ||||
| 				    { | ||||
| 					"time": 20210506153611, | ||||
| 					"user": "testUser", | ||||
| 					"section": "SYSTEM", | ||||
| 					"action": "CREATE_REPOSITORY", | ||||
| 					"severity": "ERROR", | ||||
| 					"message": "20210506153611: Error action CREATE_REPOSITORY Repository already exists" | ||||
| 				    } | ||||
| 				], | ||||
| 				"log": [ | ||||
| 				    { | ||||
| 					"time": 20210506153611, | ||||
| 					"user": "testUser", | ||||
| 					"section": "SYSTEM", | ||||
| 					"action": "CREATE_REPOSITORY", | ||||
| 					"severity": "ERROR", | ||||
| 					"message": "20210506153611: Error action CREATE_REPOSITORY Repository already exists" | ||||
| 				    } | ||||
| 				], | ||||
| 				"exception": "Repository already exists" | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		err := createRepositoryForDeploy(&config, nil, nil, &httpClient, nil) | ||||
| 		assert.NoError(t, err) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestGctsSetConfigByKeySuccess(t *testing.T) { | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| 		Client:     "000", | ||||
| 		Repository: "testRepo", | ||||
| 		Branch:     "dummyBranch", | ||||
| 		Username:   "testUser", | ||||
| 		Password:   "testPassword", | ||||
| 	} | ||||
| 	configKey := setConfigKeyBody{ | ||||
| 		Key:   "dummy_key", | ||||
| 		Value: "dummy_value", | ||||
| 	} | ||||
| 	t.Run("Set Config By key Success", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Repository == "testRepo" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 200, ResponseBody: `{ | ||||
| 				"result": { | ||||
| 				    "key": "dummy_key", | ||||
| 				    "value": "dummy_value" | ||||
| 				} | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		err := setConfigKey(&config, &httpClient, &configKey) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestGctsSetConfigByKeyFailure(t *testing.T) { | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| 		Client:     "000", | ||||
| 		Repository: "testRepoNotExists", | ||||
| 		Branch:     "dummyBranchNotExists", | ||||
| 		Username:   "testUser", | ||||
| 		Password:   "testPassword", | ||||
| 	} | ||||
| 	configKey := setConfigKeyBody{ | ||||
| 		Key:   "dummy_key", | ||||
| 		Value: "dummy_value", | ||||
| 	} | ||||
| 	t.Run("Set Config By key Success", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Repository == "testRepoNotExists" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 500, ResponseBody: `{ | ||||
| 				"exception": "No relation between system and repository" | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		err := setConfigKey(&config, &httpClient, &configKey) | ||||
|  | ||||
| 		assert.EqualError(t, err, "a http error occurred") | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestGctsDeleteConfigByKeySuccess(t *testing.T) { | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| 		Client:     "000", | ||||
| 		Repository: "testRepo", | ||||
| 		Branch:     "dummyBranch", | ||||
| 		Username:   "testUser", | ||||
| 		Password:   "testPassword", | ||||
| 	} | ||||
| 	t.Run("Delete Config By key Success", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Repository == "testRepo" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 200, ResponseBody: `{ | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		err := deleteConfigKey(&config, &httpClient, "dummy_config") | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestGctsDeleteConfigByKeyFailure(t *testing.T) { | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| 		Client:     "000", | ||||
| 		Repository: "testRepoNotExists", | ||||
| 		Branch:     "dummyBranchNotExists", | ||||
| 		Username:   "testUser", | ||||
| 		Password:   "testPassword", | ||||
| 	} | ||||
| 	t.Run("Delete Config By key Failure", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Repository == "testRepoNotExists" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 500, ResponseBody: `{ | ||||
| 				"exception": "No relation between system and repository" | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		err := deleteConfigKey(&config, &httpClient, "dummy_config") | ||||
|  | ||||
| 		assert.EqualError(t, err, "a http error occurred") | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestGctsConfigMetadataSuccess(t *testing.T) { | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| 		Client:     "000", | ||||
| 		Repository: "testRepo", | ||||
| 		Branch:     "dummyBranch", | ||||
| 		Username:   "testUser", | ||||
| 		Password:   "testPassword", | ||||
| 	} | ||||
| 	t.Run("Test Config Metadata Success", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Repository == "testRepo" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 200, ResponseBody: `{ | ||||
| 				"config": [ | ||||
| 				    { | ||||
| 					"ckey": "dummy_key_system", | ||||
| 					"ctype": "SYSTEM", | ||||
| 					"cvisible": "X", | ||||
| 					"datatype": "STRING", | ||||
| 					"defaultValue": "dummy_default_system", | ||||
| 					"description": "Dummy Key System", | ||||
| 					"category": "SYSTEM", | ||||
| 					"example": "dummy" | ||||
| 				    }, | ||||
| 				    { | ||||
| 					"ckey": "dummy_key_repo", | ||||
| 					"ctype": "REPOSITORY", | ||||
| 					"cvisible": "X", | ||||
| 					"datatype": "STRING", | ||||
| 					"defaultValue": "dummy_default", | ||||
| 					"description": "Dummy Key repository", | ||||
| 					"category": "INTERNAL", | ||||
| 					"example": "dummy" | ||||
| 				    } | ||||
| 				] | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		configMetadata, err := getConfigurationMetadata(&config, &httpClient) | ||||
|  | ||||
| 		if assert.NoError(t, err) { | ||||
| 			t.Run("Check if system config matches", func(t *testing.T) { | ||||
| 				for _, config := range configMetadata.Config { | ||||
| 					if config.Ctype == "SYSTEM" { | ||||
| 						assert.Equal(t, "dummy_key_system", config.Ckey) | ||||
| 					} else if config.Ctype == "REPOSITORY" { | ||||
| 						assert.Equal(t, "dummy_key_repo", config.Ckey) | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestGctsConfigMetadataFailure(t *testing.T) { | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHostNotregistered.com:50000", | ||||
| 		Client:     "000", | ||||
| 		Repository: "testRepo", | ||||
| 		Branch:     "dummyBranch", | ||||
| 		Username:   "testUser", | ||||
| 		Password:   "testPassword", | ||||
| 	} | ||||
| 	t.Run("Test Config Metadata Failure", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Host == "http://testHostNotregistered.com:50000" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 500, ResponseBody: `{ | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		_, err := getConfigurationMetadata(&config, &httpClient) | ||||
|  | ||||
| 		assert.EqualError(t, err, "a http error occurred") | ||||
|  | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestDeployToAbapSystemSuccess(t *testing.T) { | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| 		Client:     "000", | ||||
| 		Repository: "testRepo", | ||||
| 		Username:   "testUser", | ||||
| 		Password:   "testPassword", | ||||
| 		Scope:      "dummyScope", | ||||
| 	} | ||||
|  | ||||
| 	t.Run("Deploy to ABAP system sucess", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Repository == "testRepo" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 200, ResponseBody: `{ | ||||
| 				"result": { | ||||
| 				    "rid": "testrepo", | ||||
| 				    "name": "testRepo", | ||||
| 				    "role": "dummyRole", | ||||
| 				    "type": "dummyType", | ||||
| 				    "vsid": "dummyVsid", | ||||
| 				    "status": "CREATED", | ||||
| 				    "branch": "dummyBranch", | ||||
| 				    "url": "http://testRepoUrl.com", | ||||
| 				    "createdBy": "testUser", | ||||
| 				    "createdDate": "2021-04-14", | ||||
| 				    "config": [ | ||||
| 					{ | ||||
| 					    "key": "CLIENT_VCS_CONNTYPE", | ||||
| 					    "value": "ssl", | ||||
| 					    "category": "CONNECTION", | ||||
| 					    "scope": "local" | ||||
| 					}, | ||||
| 					{ | ||||
| 					    "key": "CLIENT_VCS_URI", | ||||
| 					    "value": "http://testRepoUrl.com", | ||||
| 					    "category": "CONNECTION", | ||||
| 					    "scope": "local" | ||||
| 					} | ||||
| 				    ], | ||||
| 				    "connection": "ssl" | ||||
| 				} | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		err := deployCommitToAbapSystem(&config, &httpClient) | ||||
| 		assert.NoError(t, err) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestGctsDeployToAbapSystemFailure(t *testing.T) { | ||||
| 	config := gctsDeployOptions{ | ||||
| 		Host:       "http://testHost.com:50000", | ||||
| 		Client:     "000", | ||||
| 		Repository: "testRepoNotExists", | ||||
| 		Username:   "testUser", | ||||
| 		Password:   "testPassword", | ||||
| 		Scope:      "dummyScope", | ||||
| 	} | ||||
| 	t.Run("Deploy to ABAP system Failure", func(t *testing.T) { | ||||
| 		var httpClient httpMockGcts | ||||
| 		if config.Repository == "testRepoNotExists" { | ||||
| 			httpClient = httpMockGcts{StatusCode: 500, ResponseBody: `{ | ||||
| 				"exception": "No relation between system and repository" | ||||
| 			    }`} | ||||
| 		} | ||||
|  | ||||
| 		err := deployCommitToAbapSystem(&config, &httpClient) | ||||
|  | ||||
| 		assert.EqualError(t, err, "a http error occurred") | ||||
|  | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestGctsSplitConfigurationToMap(t *testing.T) { | ||||
| 	config := []configMetadata{ | ||||
| 		{ | ||||
| 			Ckey:     "dummyKey1", | ||||
| 			Ctype:    "REPOSITORY", | ||||
| 			Datatype: "BOOLEAN", | ||||
| 			Example:  "X", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Ckey:     "dummyKey2", | ||||
| 			Ctype:    "REPOSITORY", | ||||
| 			Datatype: "BOOLEAN", | ||||
| 			Example:  "true", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Ckey:     "dummyKey3", | ||||
| 			Ctype:    "REPOSITORY", | ||||
| 			Datatype: "STRING", | ||||
| 			Example:  "dummyValue", | ||||
| 		}, | ||||
| 	} | ||||
| 	configMetadata := configurationMetadataBody{ | ||||
| 		Config: config, | ||||
| 	} | ||||
|  | ||||
| 	configMap := map[string]interface{}{ | ||||
| 		"dummyKey1": "true", | ||||
| 		"dummyKey2": "true", | ||||
| 		"dummyKey3": "dummyValue2", | ||||
| 	} | ||||
|  | ||||
| 	t.Run("Config Mapping test", func(t *testing.T) { | ||||
| 		repoConfig, err := splitConfigurationToMap(configMap, configMetadata) | ||||
|  | ||||
| 		if assert.NoError(t, err) { | ||||
| 			for _, config := range repoConfig { | ||||
| 				if config.Key == "dummyKey1" { | ||||
| 					assert.Equal(t, "X", config.Value) | ||||
| 				} else if config.Key == "dummyKey2" { | ||||
| 					assert.Equal(t, "true", config.Value) | ||||
| 				} else if config.Key == "dummyKey3" { | ||||
| 					assert.Equal(t, "dummyValue2", config.Value) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|   | ||||
| @@ -54,7 +54,6 @@ func rollback(config *gctsRollbackOptions, telemetryData *telemetry.CustomData, | ||||
| 		return errors.Errorf("no remote repository URL configured") | ||||
| 	} | ||||
|  | ||||
| 	parsedURL, err := url.Parse(repoInfo.Result.URL) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "could not parse remote repository URL as valid URL") | ||||
| 	} | ||||
| @@ -73,28 +72,6 @@ func rollback(config *gctsRollbackOptions, telemetryData *telemetry.CustomData, | ||||
| 			Commit:     config.Commit, | ||||
| 		} | ||||
|  | ||||
| 	} else if parsedURL.Host == "github.com" { | ||||
| 		log.Entry().Info("Remote repository domain is 'github.com'. Trying to rollback to last commit with status 'success'.") | ||||
|  | ||||
| 		commitList, err := getCommits(config, telemetryData, httpClient) | ||||
| 		if err != nil { | ||||
| 			return errors.Wrap(err, "could not get repository commits") | ||||
| 		} | ||||
|  | ||||
| 		successCommit, err := getLastSuccessfullCommit(config, telemetryData, httpClient, parsedURL, commitList) | ||||
| 		if err != nil { | ||||
| 			return errors.Wrap(err, "could not determine successful commit") | ||||
| 		} | ||||
|  | ||||
| 		deployOptions = gctsDeployOptions{ | ||||
| 			Username:   config.Username, | ||||
| 			Password:   config.Password, | ||||
| 			Host:       config.Host, | ||||
| 			Repository: config.Repository, | ||||
| 			Client:     config.Client, | ||||
| 			Commit:     successCommit, | ||||
| 		} | ||||
|  | ||||
| 	} else { | ||||
| 		repoHistory, err := getRepoHistory(config, telemetryData, httpClient) | ||||
| 		if err != nil { | ||||
| @@ -117,7 +94,7 @@ func rollback(config *gctsRollbackOptions, telemetryData *telemetry.CustomData, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	deployErr := deployCommit(&deployOptions, telemetryData, httpClient) | ||||
| 	deployErr := pullByCommit(&deployOptions, telemetryData, command, httpClient) | ||||
|  | ||||
| 	if deployErr != nil { | ||||
| 		return errors.Wrap(deployErr, "rollback commit failed") | ||||
|   | ||||
| @@ -4,7 +4,9 @@ | ||||
|  | ||||
| ## Prerequisites | ||||
|  | ||||
| With this step you can deploy a commit from a remote Git repository to a local repository on an ABAP server. If no `commit` parameter is specified, this step will pull the latest commit available on the remote repository. | ||||
| With this step you can deploy a remote Git repository to a local repository on an ABAP server. If a `commit` parameter is specified the step would pull the | ||||
| repository to the commit that was mentioned. If a `branch` is provided then the repository would be switched to the respective branch specified. The rollback flag can be used to decide if you want a rollback to the working state of the reposiotry | ||||
| in the case of a failure. | ||||
| Learn more about the SAP Git-enabled Change & Transport System (gCTS) [here](https://help.sap.com/viewer/4a368c163b08418890a406d413933ba7/201909.001/en-US/f319b168e87e42149e25e13c08d002b9.html). With gCTS, ABAP developments on ABAP servers can be maintained in Git repositories. | ||||
|  | ||||
| ## ${docGenParameters} | ||||
| @@ -23,7 +25,15 @@ gctsDeploy( | ||||
|   host: 'https://abap.server.com:port', | ||||
|   client: '000', | ||||
|   abapCredentialsId: 'ABAPUserPasswordCredentialsId', | ||||
|   repository: 'myrepo' | ||||
|   repository: 'myrepo', | ||||
|   remoteRepositoryURL: "https://remote.repository.url.com", | ||||
|   role: 'SOURCE', | ||||
|   vSID: 'ABC', | ||||
|   branch: 'branch', | ||||
|   commit: 'commit', | ||||
|   scope: 'scope', | ||||
|   rollback: false, | ||||
|   configuration: [dummyConfig: 'dummyval'] | ||||
| ) | ||||
| ``` | ||||
|  | ||||
| @@ -37,4 +47,13 @@ steps: | ||||
|     client: '000' | ||||
|     abapCredentialsId: 'ABAPUserPasswordCredentialsId' | ||||
|     repository: 'myrepo' | ||||
|     remoteRepositoryURL: "https://remote.repository.url.com" | ||||
|     role: 'SOURCE' | ||||
|     vSID: 'ABC' | ||||
|     branch: 'branch' | ||||
|     commit: 'commit' | ||||
|     scope: 'scope' | ||||
|     rollback: false | ||||
|     configuration: | ||||
|         dummyconfig: "dummyval" | ||||
| ``` | ||||
|   | ||||
							
								
								
									
										138
									
								
								resources/metadata/gctsDeploy.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								resources/metadata/gctsDeploy.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| metadata: | ||||
|   name: gctsDeploy | ||||
|   description: Deploys a Git Repository to a local Repository and then to an ABAP System | ||||
|   longDescription: | | ||||
|     The steps deploys a git repository to an ABAP System. If a repository does not exists in the system, it creates and clones it to the local | ||||
|     repository and then deploys it into the ABAP system. | ||||
|  | ||||
| spec: | ||||
|   inputs: | ||||
|     secrets: | ||||
|       - name: abapCredentialsId | ||||
|         description: Jenkins credentials ID containing username and password for authentication to the ABAP system on which you want to deploy a commit | ||||
|         type: jenkins | ||||
|     params: | ||||
|       - name: username | ||||
|         type: string | ||||
|         description: User to authenticate to the ABAP system | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         mandatory: true | ||||
|         secret: true | ||||
|         resourceRef: | ||||
|           - name: abapCredentialsId | ||||
|             type: secret | ||||
|             param: username | ||||
|       - name: password | ||||
|         type: string | ||||
|         description: Password to authenticate to the ABAP system | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         mandatory: true | ||||
|         secret: true | ||||
|         resourceRef: | ||||
|           - name: abapCredentialsId | ||||
|             type: secret | ||||
|             param: password | ||||
|       - name: repository | ||||
|         type: string | ||||
|         description: Specifies the name (ID) of the local repsitory on the ABAP system | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         mandatory: true | ||||
|       - name: host | ||||
|         type: string | ||||
|         description: Specifies the protocol and host address, including the port. Please provide in the format `<protocol>://<host>:<port>`. Supported protocols are `http` and `https`. | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         mandatory: true | ||||
|       - name: client | ||||
|         type: string | ||||
|         description: Specifies the client of the ABAP system to be addressed | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         mandatory: true | ||||
|       - name: commit | ||||
|         type: string | ||||
|         description: Specifies the commit to be deployed | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|       - name: remoteRepositoryURL | ||||
|         type: string | ||||
|         description: URL of the corresponding remote repository | ||||
|         mandatory: true | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|       - name: role | ||||
|         type: string | ||||
|         description: Role of the local repository. Choose between 'TARGET' and 'SOURCE'. Local repositories with a TARGET role will NOT be able to be the source of code changes | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         possibleValues: | ||||
|           - SOURCE | ||||
|           - TARGET | ||||
|       - name: vSID | ||||
|         type: string | ||||
|         description: Virtual SID of the local repository. The vSID corresponds to the transport route that delivers content to the remote Git repository | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|       - name: type | ||||
|         type: string | ||||
|         description: Type of the used source code management tool | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         default: GIT | ||||
|         possibleValues: | ||||
|           - GIT | ||||
|       - name: branch | ||||
|         type: string | ||||
|         description: Name of the branch to which the deploy has to be done to. | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|       - name: scope | ||||
|         type: string | ||||
|         description: The scope of the gcts deploy api call | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|       - name: rollback | ||||
|         type: bool | ||||
|         description: The rollback flag for a failure during the deploy step. A true value would mean gCTS would roll back to the last clean state | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|       - name: configuration | ||||
|         type: "map[string]interface{}" | ||||
|         description: "Defines the configuration values for the repository. This map needs to be created as `configKeyName`:`configKeyValue`" | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|           - GENERAL | ||||
|         mandatory: false | ||||
|         aliases: | ||||
|           - name: gctsRepositoryConfigurations | ||||
| @@ -1,70 +0,0 @@ | ||||
| metadata: | ||||
|   name: gctsDeploy | ||||
|   description: Pulls a commit from the remote Git repository to a local repository | ||||
|   longDescription: | | ||||
|     Pulls a commit from the corresponding remote Git repository to a specified local repository on an ABAP system. If no <commit> parameter is specified, this step will pull the latest commit available on the remote repository. | ||||
|  | ||||
| spec: | ||||
|   inputs: | ||||
|     secrets: | ||||
|       - name: abapCredentialsId | ||||
|         description: Jenkins credentials ID containing username and password for authentication to the ABAP system on which you want to deploy a commit | ||||
|         type: jenkins | ||||
|     params: | ||||
|       - name: username | ||||
|         type: string | ||||
|         description: User to authenticate to the ABAP system | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         mandatory: true | ||||
|         secret: true | ||||
|         resourceRef: | ||||
|           - name: abapCredentialsId | ||||
|             type: secret | ||||
|             param: username | ||||
|       - name: password | ||||
|         type: string | ||||
|         description: Password to authenticate to the ABAP system | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         mandatory: true | ||||
|         secret: true | ||||
|         resourceRef: | ||||
|           - name: abapCredentialsId | ||||
|             type: secret | ||||
|             param: password | ||||
|       - name: repository | ||||
|         type: string | ||||
|         description: Specifies the name (ID) of the local repsitory on the ABAP system | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         mandatory: true | ||||
|       - name: host | ||||
|         type: string | ||||
|         description: Specifies the protocol and host address, including the port. Please provide in the format `<protocol>://<host>:<port>`. Supported protocols are `http` and `https`. | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         mandatory: true | ||||
|       - name: client | ||||
|         type: string | ||||
|         description: Specifies the client of the ABAP system to be addressed | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         mandatory: true | ||||
|       - name: commit | ||||
|         type: string | ||||
|         description: Specifies the commit to be deployed | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
		Reference in New Issue
	
	Block a user