github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/abapEnvironmentCloneGitRepo.go (about) 1 package cmd 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "net/http" 8 "net/http/cookiejar" 9 "reflect" 10 "time" 11 12 "github.com/SAP/jenkins-library/pkg/abaputils" 13 "github.com/SAP/jenkins-library/pkg/command" 14 piperhttp "github.com/SAP/jenkins-library/pkg/http" 15 "github.com/SAP/jenkins-library/pkg/log" 16 "github.com/SAP/jenkins-library/pkg/telemetry" 17 "github.com/pkg/errors" 18 ) 19 20 func abapEnvironmentCloneGitRepo(config abapEnvironmentCloneGitRepoOptions, _ *telemetry.CustomData) { 21 22 c := command.Command{} 23 24 c.Stdout(log.Writer()) 25 c.Stderr(log.Writer()) 26 27 var autils = abaputils.AbapUtils{ 28 Exec: &c, 29 } 30 31 client := piperhttp.Client{} 32 // error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end 33 err := runAbapEnvironmentCloneGitRepo(&config, &autils, &client) 34 if err != nil { 35 log.Entry().WithError(err).Fatal("step execution failed") 36 } 37 } 38 39 func runAbapEnvironmentCloneGitRepo(config *abapEnvironmentCloneGitRepoOptions, com abaputils.Communication, client piperhttp.Sender) error { 40 // Mapping for options 41 subOptions := convertCloneConfig(config) 42 43 // Determine the host, user and password, either via the input parameters or via a cloud foundry service key 44 connectionDetails, errorGetInfo := com.GetAbapCommunicationArrangementInfo(subOptions, "") 45 if errorGetInfo != nil { 46 return errors.Wrap(errorGetInfo, "Parameters for the ABAP Connection not available") 47 } 48 49 // Configuring the HTTP Client and CookieJar 50 cookieJar, errorCookieJar := cookiejar.New(nil) 51 if errorCookieJar != nil { 52 return errors.Wrap(errorCookieJar, "Could not create a Cookie Jar") 53 } 54 55 client.SetOptions(piperhttp.ClientOptions{ 56 MaxRequestDuration: 180 * time.Second, 57 CookieJar: cookieJar, 58 Username: connectionDetails.User, 59 Password: connectionDetails.Password, 60 }) 61 62 errConfig := checkConfiguration(config) 63 if errConfig != nil { 64 return errors.Wrap(errConfig, "The provided configuration is not allowed") 65 } 66 67 repositories, errGetRepos := abaputils.GetRepositories(&abaputils.RepositoriesConfig{BranchName: config.BranchName, RepositoryName: config.RepositoryName, Repositories: config.Repositories}, true) 68 if errGetRepos != nil { 69 return fmt.Errorf("Something failed during the clone: %w", errGetRepos) 70 } 71 72 log.Entry().Infof("Start cloning %v repositories", len(repositories)) 73 for _, repo := range repositories { 74 75 logString := repo.GetCloneLogString() 76 errorString := "Clone of " + logString + " failed on the ABAP system" 77 78 abaputils.AddDefaultDashedLine() 79 log.Entry().Info("Start cloning " + logString) 80 abaputils.AddDefaultDashedLine() 81 82 // Triggering the Clone of the repository into the ABAP Environment system 83 uriConnectionDetails, errorTriggerClone, didCheckoutPullInstead := triggerClone(repo, connectionDetails, client) 84 if errorTriggerClone != nil { 85 return errors.Wrapf(errorTriggerClone, errorString) 86 } 87 88 if !didCheckoutPullInstead { 89 // Polling the status of the repository import on the ABAP Environment system 90 // If the repository had been cloned already, as checkout/pull has been done - polling the status is not necessary anymore 91 status, errorPollEntity := abaputils.PollEntity(repo.Name, uriConnectionDetails, client, com.GetPollIntervall()) 92 if errorPollEntity != nil { 93 return errors.Wrapf(errorPollEntity, errorString) 94 } 95 if status == "E" { 96 return errors.New("Clone of " + logString + " failed on the ABAP System") 97 } 98 log.Entry().Info("The " + logString + " was cloned successfully") 99 } 100 } 101 abaputils.AddDefaultDashedLine() 102 log.Entry().Info("All repositories were cloned successfully") 103 return nil 104 } 105 106 func checkConfiguration(config *abapEnvironmentCloneGitRepoOptions) error { 107 if config.Repositories != "" && config.RepositoryName != "" { 108 return errors.New("It is not allowed to configure the parameters `repositories`and `repositoryName` at the same time") 109 } 110 if config.Repositories == "" && config.RepositoryName == "" { 111 return errors.New("Please provide one of the following parameters: `repositories` or `repositoryName`") 112 } 113 return nil 114 } 115 116 func triggerClone(repo abaputils.Repository, cloneConnectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) (abaputils.ConnectionDetailsHTTP, error, bool) { 117 118 uriConnectionDetails := cloneConnectionDetails 119 cloneConnectionDetails.XCsrfToken = "fetch" 120 121 cloneConnectionDetails.URL = cloneConnectionDetails.URL + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Clones" 122 123 // Loging into the ABAP System - getting the x-csrf-token and cookies 124 resp, err := abaputils.GetHTTPResponse("HEAD", cloneConnectionDetails, nil, client) 125 if err != nil { 126 err = abaputils.HandleHTTPError(resp, err, "Authentication on the ABAP system failed", cloneConnectionDetails) 127 return uriConnectionDetails, err, false 128 } 129 defer resp.Body.Close() 130 131 log.Entry().WithField("StatusCode", resp.Status).WithField("ABAP Endpoint", cloneConnectionDetails.URL).Debug("Authentication on the ABAP system successful") 132 uriConnectionDetails.XCsrfToken = resp.Header.Get("X-Csrf-Token") 133 cloneConnectionDetails.XCsrfToken = uriConnectionDetails.XCsrfToken 134 135 // Trigger the Clone of a Repository 136 if repo.Name == "" { 137 return uriConnectionDetails, errors.New("An empty string was passed for the parameter 'repositoryName'"), false 138 } 139 140 jsonBody := []byte(repo.GetCloneRequestBody()) 141 resp, err = abaputils.GetHTTPResponse("POST", cloneConnectionDetails, jsonBody, client) 142 if err != nil { 143 err, alreadyCloned := handleCloneError(resp, err, cloneConnectionDetails, client, repo) 144 return uriConnectionDetails, err, alreadyCloned 145 } 146 defer resp.Body.Close() 147 log.Entry().WithField("StatusCode", resp.Status).WithField("repositoryName", repo.Name).WithField("branchName", repo.Branch).WithField("commitID", repo.CommitID).WithField("Tag", repo.Tag).Info("Triggered Clone of Repository / Software Component") 148 149 // Parse Response 150 var body abaputils.CloneEntity 151 var abapResp map[string]*json.RawMessage 152 bodyText, errRead := io.ReadAll(resp.Body) 153 if errRead != nil { 154 return uriConnectionDetails, err, false 155 } 156 if err := json.Unmarshal(bodyText, &abapResp); err != nil { 157 return uriConnectionDetails, err, false 158 } 159 if err := json.Unmarshal(*abapResp["d"], &body); err != nil { 160 return uriConnectionDetails, err, false 161 } 162 if reflect.DeepEqual(abaputils.CloneEntity{}, body) { 163 log.Entry().WithField("StatusCode", resp.Status).WithField("repositoryName", repo.Name).WithField("branchName", repo.Branch).WithField("commitID", repo.CommitID).WithField("Tag", repo.Tag).Error("Could not Clone the Repository / Software Component") 164 err := errors.New("Request to ABAP System not successful") 165 return uriConnectionDetails, err, false 166 } 167 168 // The entity "Clones" does not allow for polling. To poll the progress, the related entity "Pull" has to be called 169 // While "Clones" has the key fields UUID, SC_NAME and BRANCH_NAME, "Pull" only has the key field UUID 170 uriConnectionDetails.URL = uriConnectionDetails.URL + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Pull(uuid=guid'" + body.UUID + "')" 171 return uriConnectionDetails, nil, false 172 } 173 174 func handleCloneError(resp *http.Response, err error, cloneConnectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, repo abaputils.Repository) (returnedError error, alreadyCloned bool) { 175 alreadyCloned = false 176 returnedError = nil 177 if resp == nil { 178 log.Entry().WithError(err).WithField("ABAP Endpoint", cloneConnectionDetails.URL).Error("Request failed") 179 returnedError = errors.New("Response is nil") 180 return 181 } 182 defer resp.Body.Close() 183 errorText, errorCode, parsingError := abaputils.GetErrorDetailsFromResponse(resp) 184 if parsingError != nil { 185 returnedError = err 186 return 187 } 188 if errorCode == "A4C_A2G/257" { 189 // With the latest release, a repeated "clone" was prohibited 190 // As an intermediate workaround, we react to the error message A4C_A2G/257 that gets thrown, if the repository had already been cloned 191 // In this case, a checkout branch and a pull will be performed 192 alreadyCloned = true 193 abaputils.AddDefaultDashedLine() 194 abaputils.AddDefaultDashedLine() 195 log.Entry().Infof("%s", "The repository / software component has already been cloned on the ABAP Environment system ") 196 log.Entry().Infof("%s", "A `checkout branch` and a `pull` will be performed instead") 197 abaputils.AddDefaultDashedLine() 198 abaputils.AddDefaultDashedLine() 199 checkoutOptions := abapEnvironmentCheckoutBranchOptions{ 200 Username: cloneConnectionDetails.User, 201 Password: cloneConnectionDetails.Password, 202 Host: cloneConnectionDetails.Host, 203 RepositoryName: repo.Name, 204 BranchName: repo.Branch, 205 } 206 c := command.Command{} 207 c.Stdout(log.Writer()) 208 c.Stderr(log.Writer()) 209 com := abaputils.AbapUtils{ 210 Exec: &c, 211 } 212 returnedError = runAbapEnvironmentCheckoutBranch(&checkoutOptions, &com, client) 213 if returnedError != nil { 214 return 215 } 216 abaputils.AddDefaultDashedLine() 217 abaputils.AddDefaultDashedLine() 218 pullOptions := abapEnvironmentPullGitRepoOptions{ 219 Username: cloneConnectionDetails.User, 220 Password: cloneConnectionDetails.Password, 221 Host: cloneConnectionDetails.Host, 222 RepositoryName: repo.Name, 223 CommitID: repo.CommitID, 224 } 225 returnedError = runAbapEnvironmentPullGitRepo(&pullOptions, &com, client) 226 if returnedError != nil { 227 return 228 } 229 } else { 230 log.Entry().WithField("StatusCode", resp.Status).Error("Could not clone the " + repo.GetCloneLogString()) 231 abapError := errors.New(fmt.Sprintf("%s - %s", errorCode, errorText)) 232 returnedError = errors.Wrap(abapError, err.Error()) 233 } 234 return 235 } 236 237 func convertCloneConfig(config *abapEnvironmentCloneGitRepoOptions) abaputils.AbapEnvironmentOptions { 238 subOptions := abaputils.AbapEnvironmentOptions{} 239 240 subOptions.CfAPIEndpoint = config.CfAPIEndpoint 241 subOptions.CfServiceInstance = config.CfServiceInstance 242 subOptions.CfServiceKeyName = config.CfServiceKeyName 243 subOptions.CfOrg = config.CfOrg 244 subOptions.CfSpace = config.CfSpace 245 subOptions.Host = config.Host 246 subOptions.Password = config.Password 247 subOptions.Username = config.Username 248 return subOptions 249 }