1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00
sap-jenkins-library/cmd/gctsDeploy.go
Jordi van Liempt 0ba4c2206c
chore(deps): Replace io/ioutil package (#4494)
* update all deprecated ioutil usages

* forgotten changes

* add missing imports

* undo changing comment

* add missing 'os' import

* fix integration test

---------

Co-authored-by: I557621 <jordi.van.liempt@sap.com>
Co-authored-by: Gulom Alimov <gulomjon.alimov@sap.com>
2023-08-16 12:57:04 +02:00

911 lines
32 KiB
Go

package cmd
import (
"bytes"
"encoding/json"
"fmt"
"io"
"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
// Example: step checkmarxExecuteScan.go
httpClient := &piperhttp.Client{}
// error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end
err := gctsDeployRepository(&config, telemetryData, &c, httpClient)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func gctsDeployRepository(config *gctsDeployOptions, telemetryData *telemetry.CustomData, command command.ExecRunner, httpClient piperhttp.Sender) error {
maxRetries := -1
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,
MaxRetries: maxRetries,
TransportSkipVerification: config.SkipSSLVerification,
}
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,
QueryParameters: config.QueryParameters,
SkipSSLVerification: config.SkipSSLVerification,
}
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,
QueryParameters: config.QueryParameters,
SkipSSLVerification: config.SkipSSLVerification,
}
// 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,
SkipSSLVerification: config.SkipSSLVerification,
}
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
requestURL, urlErr := addQueryToURL(requestURL, config.QueryParameters)
if urlErr != nil {
return nil, urlErr
}
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
requestURL, urlErr := addQueryToURL(requestURL, config.QueryParameters)
if urlErr != nil {
return urlErr
}
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
requestURL, urlErr := addQueryToURL(requestURL, config.QueryParameters)
if urlErr != nil {
return nil, urlErr
}
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
requestURL, urlErr := addQueryToURL(requestURL, deployConfig.QueryParameters)
if urlErr != nil {
return urlErr
}
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
requestURL, urlErr := addQueryToURL(requestURL, deployConfig.QueryParameters)
if urlErr != nil {
return urlErr
}
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 {
return errors.Wrap(cookieErr, "creating a cookie jar failed")
}
clientOptions := piperhttp.ClientOptions{
CookieJar: cookieJar,
Username: config.Username,
Password: config.Password,
MaxRetries: -1,
TransportSkipVerification: config.SkipSSLVerification,
}
httpClient.SetOptions(clientOptions)
requestURL := config.Host +
"/sap/bc/cts_abapvcs/repository/" + config.Repository +
"/pullByCommit?sap-client=" + config.Client + "&request=" + config.Commit
requestURL, urlErr := addQueryToURL(requestURL, config.QueryParameters)
if urlErr != nil {
return urlErr
}
if config.Commit != "" {
log.Entry().Infof("preparing to deploy specified commit %v", config.Commit)
params := url.Values{}
params.Add("request", config.Commit)
requestURL = requestURL + "&" + params.Encode()
}
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 errorDumpParseErr
}
return httpErr
} else if resp == nil {
return errors.New("did not retrieve a HTTP response")
}
bodyText, readErr := io.ReadAll(resp.Body)
if readErr != nil {
return errors.Wrapf(readErr, "HTTP response body could not be read")
}
response, parsingErr := gabs.ParseJSON([]byte(bodyText))
if parsingErr != nil {
return errors.Wrapf(parsingErr, "HTTP response body could not be parsed as JSON: %v", string(bodyText))
}
log.Entry().
WithField("repository", config.Repository).
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,
MaxRetries: -1,
TransportSkipVerification: config.SkipSSLVerification,
}
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
url, urlErr := addQueryToURL(url, config.QueryParameters)
if urlErr != nil {
return urlErr
}
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?sap-client=" + config.Client
requestURL, urlErr := addQueryToURL(requestURL, config.QueryParameters)
if urlErr != nil {
return nil, urlErr
}
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
}
func addQueryToURL(requestURL string, keyValue map[string]interface{}) (string, error) {
var formattedURL string
formattedURL = requestURL
if keyValue != nil {
if strings.Contains(requestURL, "?") {
for key, value := range keyValue {
configValue := fmt.Sprint(value)
formattedURL = formattedURL + "&" + key + "=" + configValue
}
} else {
i := 0
for key, value := range keyValue {
configValue := fmt.Sprint(value)
if i == 0 {
formattedURL = requestURL + "?" + key + "=" + configValue
} else {
formattedURL = formattedURL + "&" + key + "=" + configValue
}
i++
}
}
}
if strings.Count(formattedURL, "") > 2001 {
log.Entry().Error("Url endpoint is longer than 2000 characters!")
return formattedURL, errors.New("Url endpoint is longer than 2000 characters!")
}
return formattedURL, 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"`
}