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 // 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 { var maxRetries int 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, } 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 { return errors.Wrap(cookieErr, "creating a cookie jar failed") } clientOptions := piperhttp.ClientOptions{ CookieJar: cookieJar, Username: config.Username, Password: config.Password, } httpClient.SetOptions(clientOptions) requestURL := config.Host + "/sap/bc/cts_abapvcs/repository/" + config.Repository + "/pullByCommit?sap-client=" + config.Client + "&request=" + config.Commit 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 := ioutil.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, } 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"` }