github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/integrationArtifactDeploy.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "io" 6 "net/http" 7 "time" 8 9 "github.com/Jeffail/gabs/v2" 10 "github.com/SAP/jenkins-library/pkg/command" 11 "github.com/SAP/jenkins-library/pkg/cpi" 12 piperhttp "github.com/SAP/jenkins-library/pkg/http" 13 "github.com/SAP/jenkins-library/pkg/log" 14 "github.com/SAP/jenkins-library/pkg/telemetry" 15 "github.com/pkg/errors" 16 ) 17 18 const retryCount = 14 19 20 type integrationArtifactDeployUtils interface { 21 command.ExecRunner 22 23 // Add more methods here, or embed additional interfaces, or remove/replace as required. 24 // The integrationArtifactDeployUtils interface should be descriptive of your runtime dependencies, 25 // i.e. include everything you need to be able to mock in tests. 26 // Unit tests shall be executable in parallel (not depend on global state), and don't (re-)test dependencies. 27 } 28 29 type integrationArtifactDeployUtilsBundle struct { 30 *command.Command 31 32 // Embed more structs as necessary to implement methods or interfaces you add to integrationArtifactDeployUtils. 33 // Structs embedded in this way must each have a unique set of methods attached. 34 // If there is no struct which implements the method you need, attach the method to 35 // integrationArtifactDeployUtilsBundle and forward to the implementation of the dependency. 36 } 37 38 func newIntegrationArtifactDeployUtils() integrationArtifactDeployUtils { 39 utils := integrationArtifactDeployUtilsBundle{ 40 Command: &command.Command{}, 41 } 42 // Reroute command output to logging framework 43 utils.Stdout(log.Writer()) 44 utils.Stderr(log.Writer()) 45 return &utils 46 } 47 48 func integrationArtifactDeploy(config integrationArtifactDeployOptions, telemetryData *telemetry.CustomData) { 49 // Utils can be used wherever the command.ExecRunner interface is expected. 50 // It can also be used for example as a mavenExecRunner. 51 utils := newIntegrationArtifactDeployUtils() 52 utils.Stdout(log.Writer()) 53 httpClient := &piperhttp.Client{} 54 55 // For HTTP calls import piperhttp "github.com/SAP/jenkins-library/pkg/http" 56 // and use a &piperhttp.Client{} in a custom system 57 // Example: step checkmarxExecuteScan.go 58 59 // Error situations should be bubbled up until they reach the line below which will then stop execution 60 // through the log.Entry().Fatal() call leading to an os.Exit(1) in the end. 61 err := runIntegrationArtifactDeploy(&config, telemetryData, httpClient) 62 if err != nil { 63 log.Entry().WithError(err).Fatal("step execution failed") 64 } 65 } 66 67 func runIntegrationArtifactDeploy(config *integrationArtifactDeployOptions, telemetryData *telemetry.CustomData, httpClient piperhttp.Sender) error { 68 clientOptions := piperhttp.ClientOptions{} 69 header := make(http.Header) 70 header.Add("Accept", "application/json") 71 serviceKey, err := cpi.ReadCpiServiceKey(config.APIServiceKey) 72 if err != nil { 73 return err 74 } 75 deployURL := fmt.Sprintf("%s/api/v1/DeployIntegrationDesigntimeArtifact?Id='%s'&Version='%s'", serviceKey.OAuth.Host, config.IntegrationFlowID, "Active") 76 77 tokenParameters := cpi.TokenParameters{TokenURL: serviceKey.OAuth.OAuthTokenProviderURL, Username: serviceKey.OAuth.ClientID, Password: serviceKey.OAuth.ClientSecret, Client: httpClient} 78 token, err := cpi.CommonUtils.GetBearerToken(tokenParameters) 79 if err != nil { 80 return errors.Wrap(err, "failed to fetch Bearer Token") 81 } 82 clientOptions.Token = fmt.Sprintf("Bearer %s", token) 83 httpClient.SetOptions(clientOptions) 84 httpMethod := "POST" 85 deployResp, httpErr := httpClient.SendRequest(httpMethod, deployURL, nil, header, nil) 86 if httpErr != nil { 87 return errors.Wrapf(httpErr, "HTTP %v request to %v failed with error", httpMethod, deployURL) 88 } 89 90 if deployResp != nil && deployResp.Body != nil { 91 defer deployResp.Body.Close() 92 } 93 94 if deployResp == nil { 95 return errors.Errorf("did not retrieve a HTTP response") 96 } 97 98 if deployResp.StatusCode == http.StatusAccepted { 99 log.Entry(). 100 WithField("IntegrationFlowID", config.IntegrationFlowID). 101 Info("successfully deployed into CPI runtime") 102 taskId, readErr := io.ReadAll(deployResp.Body) 103 if readErr != nil { 104 return errors.Wrap(readErr, "Task Id not found. HTTP response body could not be read.") 105 } 106 deploymentError := pollIFlowDeploymentStatus(string(taskId), retryCount, config, httpClient, serviceKey.OAuth.Host) 107 return deploymentError 108 } 109 responseBody, readErr := io.ReadAll(deployResp.Body) 110 111 if readErr != nil { 112 return errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", deployResp.StatusCode) 113 } 114 log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code : %v", string(responseBody), deployResp.StatusCode) 115 return errors.Errorf("integration flow deployment failed, response Status code: %v", deployResp.StatusCode) 116 } 117 118 // pollIFlowDeploymentStatus - Poll the integration flow deployment status, return status or error details 119 func pollIFlowDeploymentStatus(taskId string, retryCount int, config *integrationArtifactDeployOptions, httpClient piperhttp.Sender, apiHost string) error { 120 121 if retryCount <= 0 { 122 return errors.New("failed to start integration artifact after retrying several times") 123 } 124 deployStatus, err := getIntegrationArtifactDeployStatus(config, httpClient, apiHost, taskId) 125 if err != nil { 126 return err 127 } 128 129 //if artifact starting, then retry based on provided retry count 130 //with specific delay between each retry 131 if deployStatus == "DEPLOYING" { 132 // Calling Sleep method 133 sleepTime := int(retryCount * 3) 134 time.Sleep(time.Duration(sleepTime) * time.Second) 135 retryCount-- 136 return pollIFlowDeploymentStatus(taskId, retryCount, config, httpClient, apiHost) 137 } 138 139 //if artifact started, then just return 140 if deployStatus == "SUCCESS" { 141 return nil 142 } 143 144 //if error return immediately with error details 145 if deployStatus == "FAIL" || deployStatus == "FAIL_ON_LICENSE_ERROR" { 146 resp, err := getIntegrationArtifactDeployError(config, httpClient, apiHost) 147 if err != nil { 148 return err 149 } 150 return errors.New(resp) 151 } 152 return nil 153 } 154 155 // GetHTTPErrorMessage - Return HTTP failure message 156 func getHTTPErrorMessage(httpErr error, response *http.Response, httpMethod, statusURL string) (string, error) { 157 responseBody, readErr := io.ReadAll(response.Body) 158 if readErr != nil { 159 return "", errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", response.StatusCode) 160 } 161 log.Entry().Errorf("a HTTP error occurred! Response body: %v, response status code: %v", string(responseBody), response.StatusCode) 162 return "", errors.Wrapf(httpErr, "HTTP %v request to %v failed with error: %v", httpMethod, statusURL, responseBody) 163 } 164 165 // getIntegrationArtifactDeployStatus - Get integration artifact Deploy Status 166 func getIntegrationArtifactDeployStatus(config *integrationArtifactDeployOptions, httpClient piperhttp.Sender, apiHost string, taskId string) (string, error) { 167 httpMethod := "GET" 168 header := make(http.Header) 169 header.Add("content-type", "application/json") 170 header.Add("Accept", "application/json") 171 deployStatusURL := fmt.Sprintf("%s/api/v1/BuildAndDeployStatus(TaskId='%s')", apiHost, taskId) 172 deployStatusResp, httpErr := httpClient.SendRequest(httpMethod, deployStatusURL, nil, header, nil) 173 174 if deployStatusResp != nil && deployStatusResp.Body != nil { 175 defer deployStatusResp.Body.Close() 176 } 177 178 if deployStatusResp == nil { 179 return "", errors.Errorf("did not retrieve a HTTP response: %v", httpErr) 180 } 181 182 if deployStatusResp.StatusCode == http.StatusOK { 183 log.Entry(). 184 WithField("IntegrationFlowID", config.IntegrationFlowID). 185 Info("Successfully started integration flow artefact in CPI runtime") 186 187 bodyText, readErr := io.ReadAll(deployStatusResp.Body) 188 if readErr != nil { 189 return "", errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", deployStatusResp.StatusCode) 190 } 191 jsonResponse, parsingErr := gabs.ParseJSON([]byte(bodyText)) 192 if parsingErr != nil { 193 return "", errors.Wrapf(parsingErr, "HTTP response body could not be parsed as JSON: %v", string(bodyText)) 194 } 195 deployStatus := jsonResponse.Path("d.Status").Data().(string) 196 return deployStatus, nil 197 } 198 if httpErr != nil { 199 return getHTTPErrorMessage(httpErr, deployStatusResp, httpMethod, deployStatusURL) 200 } 201 return "", errors.Errorf("failed to get Integration Flow artefact runtime status, response Status code: %v", deployStatusResp.StatusCode) 202 } 203 204 // getIntegrationArtifactDeployError - Get integration artifact deploy error details 205 func getIntegrationArtifactDeployError(config *integrationArtifactDeployOptions, httpClient piperhttp.Sender, apiHost string) (string, error) { 206 httpMethod := "GET" 207 header := make(http.Header) 208 header.Add("content-type", "application/json") 209 errorStatusURL := fmt.Sprintf("%s/api/v1/IntegrationRuntimeArtifacts('%s')/ErrorInformation/$value", apiHost, config.IntegrationFlowID) 210 errorStatusResp, httpErr := httpClient.SendRequest(httpMethod, errorStatusURL, nil, header, nil) 211 212 if errorStatusResp != nil && errorStatusResp.Body != nil { 213 defer errorStatusResp.Body.Close() 214 } 215 216 if errorStatusResp == nil { 217 return "", errors.Errorf("did not retrieve a HTTP response: %v", httpErr) 218 } 219 220 if errorStatusResp.StatusCode == http.StatusOK { 221 log.Entry(). 222 WithField("IntegrationFlowID", config.IntegrationFlowID). 223 Info("Successfully retrieved Integration Flow artefact deploy error details") 224 responseBody, readErr := io.ReadAll(errorStatusResp.Body) 225 if readErr != nil { 226 return "", errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", errorStatusResp.StatusCode) 227 } 228 log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code: %v", string(responseBody), errorStatusResp.StatusCode) 229 errorDetails := string(responseBody) 230 return errorDetails, nil 231 } 232 if httpErr != nil { 233 return getHTTPErrorMessage(httpErr, errorStatusResp, httpMethod, errorStatusURL) 234 } 235 return "", errors.Errorf("failed to get Integration Flow artefact deploy error details, response Status code: %v", errorStatusResp.StatusCode) 236 }