github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/integrationArtifactTriggerIntegrationTest.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"os"
    10  
    11  	"github.com/SAP/jenkins-library/pkg/command"
    12  	"github.com/SAP/jenkins-library/pkg/cpi"
    13  	piperhttp "github.com/SAP/jenkins-library/pkg/http"
    14  	"github.com/SAP/jenkins-library/pkg/log"
    15  	"github.com/SAP/jenkins-library/pkg/piperutils"
    16  	"github.com/SAP/jenkins-library/pkg/telemetry"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  type integrationArtifactTriggerIntegrationTestUtils interface {
    21  	command.ExecRunner
    22  
    23  	FileExists(filename string) (bool, error)
    24  
    25  	// Add more methods here, or embed additional interfaces, or remove/replace as required.
    26  	// The integrationArtifactTriggerIntegrationTestUtils interface should be descriptive of your runtime dependencies,
    27  	// i.e. include everything you need to be able to mock in tests.
    28  	// Unit tests shall be executable in parallel (not depend on global state), and don't (re-)test dependencies.
    29  }
    30  
    31  type integrationArtifactTriggerIntegrationTestUtilsBundle struct {
    32  	*command.Command
    33  	*piperutils.Files
    34  
    35  	// Embed more structs as necessary to implement methods or interfaces you add to integrationArtifactTriggerIntegrationTestUtils.
    36  	// Structs embedded in this way must each have a unique set of methods attached.
    37  	// If there is no struct which implements the method you need, attach the method to
    38  	// integrationArtifactTriggerIntegrationTestUtilsBundle and forward to the implementation of the dependency.
    39  }
    40  
    41  func newIntegrationArtifactTriggerIntegrationTestUtils() integrationArtifactTriggerIntegrationTestUtils {
    42  	utils := integrationArtifactTriggerIntegrationTestUtilsBundle{
    43  		Command: &command.Command{},
    44  		Files:   &piperutils.Files{},
    45  	}
    46  	// Reroute command output to logging framework
    47  	utils.Stdout(log.Writer())
    48  	utils.Stderr(log.Writer())
    49  	return &utils
    50  }
    51  
    52  func integrationArtifactTriggerIntegrationTest(config integrationArtifactTriggerIntegrationTestOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *integrationArtifactTriggerIntegrationTestCommonPipelineEnvironment) {
    53  	// Utils can be used wherever the command.ExecRunner interface is expected.
    54  	// It can also be used for example as a mavenExecRunner.
    55  	utils := newIntegrationArtifactTriggerIntegrationTestUtils()
    56  	httpClient := &piperhttp.Client{}
    57  	// For HTTP calls import  piperhttp "github.com/SAP/jenkins-library/pkg/http"
    58  	// and use a  &piperhttp.Client{} in a custom system
    59  	// Example: step checkmarxExecuteScan.go
    60  
    61  	// Error situations should be bubbled up until they reach the line below which will then stop execution
    62  	// through the log.Entry().Fatal() call leading to an os.Exit(1) in the end.
    63  	err := runIntegrationArtifactTriggerIntegrationTest(&config, utils, httpClient, commonPipelineEnvironment)
    64  	if err != nil {
    65  		log.Entry().WithError(err).Fatal("step execution failed")
    66  	}
    67  }
    68  
    69  func runIntegrationArtifactTriggerIntegrationTest(config *integrationArtifactTriggerIntegrationTestOptions, utils integrationArtifactTriggerIntegrationTestUtils, httpClient piperhttp.Sender, commonPipelineEnvironment *integrationArtifactTriggerIntegrationTestCommonPipelineEnvironment) error {
    70  	var getServiceEndpointCommonPipelineEnvironment integrationArtifactGetServiceEndpointCommonPipelineEnvironment
    71  	var serviceEndpointUrl string
    72  	if len(config.IntegrationFlowServiceEndpointURL) > 0 {
    73  		serviceEndpointUrl = config.IntegrationFlowServiceEndpointURL
    74  	} else {
    75  		serviceEndpointUrl = getServiceEndpointCommonPipelineEnvironment.custom.integrationFlowServiceEndpoint
    76  		if len(serviceEndpointUrl) == 0 {
    77  			log.SetErrorCategory(log.ErrorConfiguration)
    78  			return fmt.Errorf("IFlowServiceEndpointURL not set")
    79  		}
    80  	}
    81  	log.Entry().Info("The Service URL : ", serviceEndpointUrl)
    82  
    83  	// Here we trigger the iFlow Service Endpoint.
    84  	IFlowErr := callIFlowURL(config, utils, httpClient, serviceEndpointUrl, commonPipelineEnvironment)
    85  	if IFlowErr != nil {
    86  		log.SetErrorCategory(log.ErrorService)
    87  		return fmt.Errorf("failed to execute iFlow: %w", IFlowErr)
    88  	}
    89  
    90  	return nil
    91  }
    92  
    93  func callIFlowURL(
    94  	config *integrationArtifactTriggerIntegrationTestOptions,
    95  	utils integrationArtifactTriggerIntegrationTestUtils,
    96  	httpIFlowClient piperhttp.Sender,
    97  	serviceEndpointUrl string,
    98  	commonPipelineEnvironment *integrationArtifactTriggerIntegrationTestCommonPipelineEnvironment) error {
    99  
   100  	var fileBody []byte
   101  	var httpMethod string
   102  	var header http.Header
   103  	if len(config.MessageBodyPath) > 0 {
   104  		if len(config.ContentType) == 0 {
   105  			log.SetErrorCategory(log.ErrorConfiguration)
   106  			return fmt.Errorf("message body file %s given, but no ContentType", config.MessageBodyPath)
   107  		}
   108  		exists, err := utils.FileExists(config.MessageBodyPath)
   109  		if err != nil {
   110  			log.SetErrorCategory(log.ErrorUndefined)
   111  			// Always wrap non-descriptive errors to enrich them with context for when they appear in the log:
   112  			return fmt.Errorf("failed to check message body file %s: %w", config.MessageBodyPath, err)
   113  		}
   114  		if !exists {
   115  			log.SetErrorCategory(log.ErrorConfiguration)
   116  			return fmt.Errorf("message body file %s configured, but not found", config.MessageBodyPath)
   117  		}
   118  
   119  		var fileErr error
   120  		fileBody, fileErr = os.ReadFile(config.MessageBodyPath)
   121  		if fileErr != nil {
   122  			log.SetErrorCategory(log.ErrorUndefined)
   123  			return fmt.Errorf("failed to read file %s: %w", config.MessageBodyPath, fileErr)
   124  		}
   125  		httpMethod = "POST"
   126  		header = make(http.Header)
   127  		header.Add("Content-Type", config.ContentType)
   128  	} else {
   129  		httpMethod = "GET"
   130  	}
   131  
   132  	serviceKey, err := cpi.ReadCpiServiceKey(config.IntegrationFlowServiceKey)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	clientOptions := piperhttp.ClientOptions{}
   137  	tokenParameters := cpi.TokenParameters{TokenURL: serviceKey.OAuth.OAuthTokenProviderURL, Username: serviceKey.OAuth.ClientID, Password: serviceKey.OAuth.ClientSecret, Client: httpIFlowClient}
   138  	token, err := cpi.CommonUtils.GetBearerToken(tokenParameters)
   139  	if err != nil {
   140  		return errors.Wrap(err, "failed to fetch Bearer Token")
   141  	}
   142  	clientOptions.Token = fmt.Sprintf("Bearer %s", token)
   143  	clientOptions.MaxRetries = -1
   144  	httpIFlowClient.SetOptions(clientOptions)
   145  	iFlowResp, httpErr := httpIFlowClient.SendRequest(httpMethod, serviceEndpointUrl, bytes.NewBuffer(fileBody), header, nil)
   146  
   147  	if httpErr != nil {
   148  		return errors.Wrapf(httpErr, "HTTP %q request to %q failed with error", httpMethod, serviceEndpointUrl)
   149  	}
   150  
   151  	if iFlowResp == nil {
   152  		return errors.Errorf("did not retrieve any HTTP response")
   153  	}
   154  
   155  	if iFlowResp.StatusCode < 400 {
   156  		log.Entry().
   157  			WithField(config.IntegrationFlowID, serviceEndpointUrl).
   158  			Infof("successfully triggered %s with status code %d", serviceEndpointUrl, iFlowResp.StatusCode)
   159  		bodyText, readErr := io.ReadAll(iFlowResp.Body)
   160  		if readErr != nil {
   161  			log.Entry().Warnf("HTTP response body could not be read. Error: %s", readErr.Error())
   162  		} else if len(bodyText) > 0 {
   163  			commonPipelineEnvironment.custom.integrationFlowTriggerIntegrationTestResponseBody = string(bodyText)
   164  		}
   165  		headersJson, err := json.Marshal(iFlowResp.Header)
   166  		if err != nil {
   167  			log.Entry().Warnf("HTTP response headers could not be marshalled. Error: %s", err.Error())
   168  		} else {
   169  			commonPipelineEnvironment.custom.integrationFlowTriggerIntegrationTestResponseHeaders = string(headersJson)
   170  		}
   171  	} else {
   172  		return fmt.Errorf("request %s failed with response code %d", serviceEndpointUrl, iFlowResp.StatusCode)
   173  	}
   174  
   175  	return nil
   176  }