github.com/xgoffin/jenkins-library@v1.154.0/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  }