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 }