github.com/jaylevin/jenkins-library@v1.230.4/cmd/integrationArtifactDeploy.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "io/ioutil" 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 deploymentError := pollIFlowDeploymentStatus(retryCount, config, httpClient, serviceKey.OAuth.Host) 103 return deploymentError 104 } 105 responseBody, readErr := ioutil.ReadAll(deployResp.Body) 106 107 if readErr != nil { 108 return errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", deployResp.StatusCode) 109 } 110 log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code : %v", string(responseBody), deployResp.StatusCode) 111 return errors.Errorf("integration flow deployment failed, response Status code: %v", deployResp.StatusCode) 112 } 113 114 //pollIFlowDeploymentStatus - Poll the integration flow deployment status, return status or error details 115 func pollIFlowDeploymentStatus(retryCount int, config *integrationArtifactDeployOptions, httpClient piperhttp.Sender, apiHost string) error { 116 117 if retryCount <= 0 { 118 return errors.New("failed to start integration artifact after retrying several times") 119 } 120 deployStatus, err := getIntegrationArtifactDeployStatus(config, httpClient, apiHost) 121 if err != nil { 122 return err 123 } 124 125 //if artifact starting, then retry based on provided retry count 126 //with specific delay between each retry 127 if deployStatus == "STARTING" { 128 // Calling Sleep method 129 sleepTime := int(retryCount * 3) 130 time.Sleep(time.Duration(sleepTime) * time.Second) 131 retryCount-- 132 return pollIFlowDeploymentStatus(retryCount, config, httpClient, apiHost) 133 } 134 135 //if artifact started, then just return 136 if deployStatus == "STARTED" { 137 return nil 138 } 139 140 //if error return immediately with error details 141 if deployStatus == "ERROR" { 142 resp, err := getIntegrationArtifactDeployError(config, httpClient, apiHost) 143 if err != nil { 144 return err 145 } 146 return errors.New(resp) 147 } 148 return nil 149 } 150 151 //GetHTTPErrorMessage - Return HTTP failure message 152 func getHTTPErrorMessage(httpErr error, response *http.Response, httpMethod, statusURL string) (string, error) { 153 responseBody, readErr := ioutil.ReadAll(response.Body) 154 if readErr != nil { 155 return "", errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", response.StatusCode) 156 } 157 log.Entry().Errorf("a HTTP error occurred! Response body: %v, response status code: %v", string(responseBody), response.StatusCode) 158 return "", errors.Wrapf(httpErr, "HTTP %v request to %v failed with error: %v", httpMethod, statusURL, responseBody) 159 } 160 161 //getIntegrationArtifactDeployStatus - Get integration artifact Deploy Status 162 func getIntegrationArtifactDeployStatus(config *integrationArtifactDeployOptions, httpClient piperhttp.Sender, apiHost string) (string, error) { 163 httpMethod := "GET" 164 header := make(http.Header) 165 header.Add("content-type", "application/json") 166 header.Add("Accept", "application/json") 167 deployStatusURL := fmt.Sprintf("%s/api/v1/IntegrationRuntimeArtifacts('%s')", apiHost, config.IntegrationFlowID) 168 deployStatusResp, httpErr := httpClient.SendRequest(httpMethod, deployStatusURL, nil, header, nil) 169 170 if deployStatusResp != nil && deployStatusResp.Body != nil { 171 defer deployStatusResp.Body.Close() 172 } 173 174 if deployStatusResp == nil { 175 return "", errors.Errorf("did not retrieve a HTTP response: %v", httpErr) 176 } 177 178 if deployStatusResp.StatusCode == http.StatusOK { 179 log.Entry(). 180 WithField("IntegrationFlowID", config.IntegrationFlowID). 181 Info("Successfully started integration flow artefact in CPI runtime") 182 183 bodyText, readErr := ioutil.ReadAll(deployStatusResp.Body) 184 if readErr != nil { 185 return "", errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", deployStatusResp.StatusCode) 186 } 187 jsonResponse, parsingErr := gabs.ParseJSON([]byte(bodyText)) 188 if parsingErr != nil { 189 return "", errors.Wrapf(parsingErr, "HTTP response body could not be parsed as JSON: %v", string(bodyText)) 190 } 191 deployStatus := jsonResponse.Path("d.Status").Data().(string) 192 return deployStatus, nil 193 } 194 if httpErr != nil { 195 return getHTTPErrorMessage(httpErr, deployStatusResp, httpMethod, deployStatusURL) 196 } 197 return "", errors.Errorf("failed to get Integration Flow artefact runtime status, response Status code: %v", deployStatusResp.StatusCode) 198 } 199 200 //getIntegrationArtifactDeployError - Get integration artifact deploy error details 201 func getIntegrationArtifactDeployError(config *integrationArtifactDeployOptions, httpClient piperhttp.Sender, apiHost string) (string, error) { 202 httpMethod := "GET" 203 header := make(http.Header) 204 header.Add("content-type", "application/json") 205 errorStatusURL := fmt.Sprintf("%s/api/v1/IntegrationRuntimeArtifacts('%s')/ErrorInformation/$value", apiHost, config.IntegrationFlowID) 206 errorStatusResp, httpErr := httpClient.SendRequest(httpMethod, errorStatusURL, nil, header, nil) 207 208 if errorStatusResp != nil && errorStatusResp.Body != nil { 209 defer errorStatusResp.Body.Close() 210 } 211 212 if errorStatusResp == nil { 213 return "", errors.Errorf("did not retrieve a HTTP response: %v", httpErr) 214 } 215 216 if errorStatusResp.StatusCode == http.StatusOK { 217 log.Entry(). 218 WithField("IntegrationFlowID", config.IntegrationFlowID). 219 Info("Successfully retrieved Integration Flow artefact deploy error details") 220 responseBody, readErr := ioutil.ReadAll(errorStatusResp.Body) 221 if readErr != nil { 222 return "", errors.Wrapf(readErr, "HTTP response body could not be read, response status code: %v", errorStatusResp.StatusCode) 223 } 224 log.Entry().Errorf("a HTTP error occurred! Response body: %v, Response status code: %v", string(responseBody), errorStatusResp.StatusCode) 225 errorDetails := string(responseBody) 226 return errorDetails, nil 227 } 228 if httpErr != nil { 229 return getHTTPErrorMessage(httpErr, errorStatusResp, httpMethod, errorStatusURL) 230 } 231 return "", errors.Errorf("failed to get Integration Flow artefact deploy error details, response Status code: %v", errorStatusResp.StatusCode) 232 }