2019-12-09 18:35:31 +02:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
2020-01-14 11:29:50 +02:00
|
|
|
"bytes"
|
2020-05-25 19:48:59 +02:00
|
|
|
"context"
|
2020-11-09 12:47:03 +02:00
|
|
|
"crypto/tls"
|
2021-08-19 11:29:33 +02:00
|
|
|
"crypto/x509"
|
2020-07-14 10:58:57 +02:00
|
|
|
"encoding/json"
|
|
|
|
"encoding/xml"
|
2019-12-09 18:35:31 +02:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2020-07-14 10:58:57 +02:00
|
|
|
"io/ioutil"
|
2020-01-14 11:29:50 +02:00
|
|
|
"mime/multipart"
|
2020-03-23 16:02:22 +02:00
|
|
|
"net"
|
2019-12-09 18:35:31 +02:00
|
|
|
"net/http"
|
[TMS] Reimplement tmsUpload step in Go (#3399)
* Initially generated tmsUpload<...> files
* First provisioning of parameters supported by tmsUpload step
* Refer to Go step from tmsUpload.groovy
* Initial client implementation
* Reverting line delimiters in tmsUpoad.groovy back to Unix ones
* Temporarily remove when-condition for Release stage
* Define useGoStep parameter in tmsUpload.groovy
* Unstash buildResult if useGoStep is true
* No unstashing and empty credentials, when using go step
* Register TmsUploadCommand in piper.go
* Cleanup groovy-related changes - they will be temporarily implemented in a different repo
* Make getting OAuth token success
* Look through the code and cleanup it a bit
* Read service key from Jenkins credentials store
* Provide initial set of unit tests for methods in /pkg/tms/tms.go file
* Minor improvements on logging response on http call error
* Check, if positive HTTP status code is as expected
* Cleanup tms.yaml file, provide additional unit test for tms.go
* Provide unit test for the case, when request body contains spaces
* Specify nodeExtDescriptorMapping parameter as of type map in tms.yaml
* Implement client method for getting nodes
* Write tests for GetNodes method
* Add GetMtaExtDescriptor client method and cover it with unit tests
* Provide first implementation for Update- and UploadMtaExtDescriptor
client methods
* Provide first implementation for Update- and UploadMtaExtDescriptor
client methods
* Provide UploadFile and UploadFileToNode client methods
* Provide tests for Update- and UploadMtaExtDescriptor client methods
* Write tests for FileUpload and FileUploadToNode client methods
* Minor corrections
* Remove some TODO comments
* Rename some of response structures
* Revert change for line delimiters in cmd/piper.go
* Add uploadType string parameter to UploadFile and UploadRequest methods
of uploader mock to reflect the changed Uploader implementation
* Start to implement execution logic in tmsUpload.go
* Changes in tms.yaml file
- remove resources from inputs in tms.yaml and implement mtaPath
parameter settings in the yaml file the same way, as it is done in
cloudFoundryDeploy.yaml
- rename tms.yaml to tmsUpload.yaml, since some generation policy
changed meanwhile
* Rename tms.yaml to tmsUpload.yaml and do go generate
* Use provided proxy on communication with UAA and TMS
* Set proxy even before getting OAuth token
* Further implementation of tmsUpload.go
* Continuation on implementing the tmsUpload.go executor
* Get mtarFilePath and git commitId from commonPipelineEnvironment, if
they are missing in configuration file + minor changes
* Implement a happy path test for tmsUpload logic
* Cover with unit tests main happy and error paths of tmsUpload.go logic
* Extend set of unit tests for tmsUpload.go
* Eliminate some TODOs, extend unit tests for tmsUpload.go
* Delete some TODOs
* Remove a couple of more TODOs from tms_test.go file
* Provide additional unit test for error due unexpected positive http
status code on upload
* Revert back line delimiters in cmd/piper.go
* Comment out file uploading calls in tmsUpload.go
* Run go generate to update generated files
* Convert line delimiters in tmsUpload.yaml to Unix ones, as well as
provide new line character in the end of the file, plus minor fix for
logging in tmsUpload.go file (pipeline complained)
* Correct description of a parameter in tmsUpload.yaml, extend unit tests
to check for trimming a slash in the end of TMS url for client methods
that do upload
* [minor] Add a comment in the test code
* Add stashContent parameter to do unstashing in tmsUpload.groovy, remove
some of the clarified TODOs
* Uncomment uploading file calls in tmsUpload.go, declare buildResult
stash in tmsUpload.yaml
* Remove clarified TODOs from the tmsUpload.go file
* Run go fmt for jenkins-library/pkg/tms
* Do not get explicitly values from common pipeline environment - all
configurations are provided in yaml file
* Remove unused struct from tmsUpload_test.go
* Run go fmt jenkins-library\pkg\tms
* Revise descriptions of parameters provided in tmsUpload.yaml file
* Specify STAGES scope for tmsUpload parameters
* Provide STAGES scope for the tmsUpload parameters, provide default value
for stashContent parameter
* Remove trailing space from tmsUpload.yaml
* Provide unit tests for proxy-related changes in http.go file
* Improve proxy implementation in tmsUpload.go file
* Make tmsServiceKey again a mandatory parameter
* Run go generate command to make the generated files correspond the yaml
state
* Change line delimiters back to Unix ones (were switched while resolving
the conflicts)
* Remove trailing spaces from tmsUpload.yaml
* Minor change in a comment to trigger pipelines with commit
* Improve checks for zero-structs and for empty maps, as well as use
different package to read files in the tests
* Revert line endings in http.go
* Revert comments formatting changes in files that do not belong to the tmsUpload step
2022-08-30 10:16:09 +02:00
|
|
|
"net/url"
|
2021-08-19 11:29:33 +02:00
|
|
|
"path"
|
|
|
|
"path/filepath"
|
2020-01-14 11:29:50 +02:00
|
|
|
"strings"
|
2019-12-09 18:35:31 +02:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/SAP/jenkins-library/pkg/log"
|
2021-06-23 14:41:52 +02:00
|
|
|
"github.com/SAP/jenkins-library/pkg/piperutils"
|
2020-11-11 14:35:53 +02:00
|
|
|
"github.com/hashicorp/go-retryablehttp"
|
2020-05-25 19:48:59 +02:00
|
|
|
"github.com/motemen/go-nuts/roundtime"
|
2019-12-09 18:35:31 +02:00
|
|
|
"github.com/pkg/errors"
|
2020-01-14 11:29:50 +02:00
|
|
|
"github.com/sirupsen/logrus"
|
2019-12-09 18:35:31 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Client defines an http client object
|
|
|
|
type Client struct {
|
2020-11-09 12:47:03 +02:00
|
|
|
maxRequestDuration time.Duration
|
2020-11-11 14:35:53 +02:00
|
|
|
maxRetries int
|
2020-11-09 12:47:03 +02:00
|
|
|
transportTimeout time.Duration
|
|
|
|
transportSkipVerification bool
|
[TMS] Reimplement tmsUpload step in Go (#3399)
* Initially generated tmsUpload<...> files
* First provisioning of parameters supported by tmsUpload step
* Refer to Go step from tmsUpload.groovy
* Initial client implementation
* Reverting line delimiters in tmsUpoad.groovy back to Unix ones
* Temporarily remove when-condition for Release stage
* Define useGoStep parameter in tmsUpload.groovy
* Unstash buildResult if useGoStep is true
* No unstashing and empty credentials, when using go step
* Register TmsUploadCommand in piper.go
* Cleanup groovy-related changes - they will be temporarily implemented in a different repo
* Make getting OAuth token success
* Look through the code and cleanup it a bit
* Read service key from Jenkins credentials store
* Provide initial set of unit tests for methods in /pkg/tms/tms.go file
* Minor improvements on logging response on http call error
* Check, if positive HTTP status code is as expected
* Cleanup tms.yaml file, provide additional unit test for tms.go
* Provide unit test for the case, when request body contains spaces
* Specify nodeExtDescriptorMapping parameter as of type map in tms.yaml
* Implement client method for getting nodes
* Write tests for GetNodes method
* Add GetMtaExtDescriptor client method and cover it with unit tests
* Provide first implementation for Update- and UploadMtaExtDescriptor
client methods
* Provide first implementation for Update- and UploadMtaExtDescriptor
client methods
* Provide UploadFile and UploadFileToNode client methods
* Provide tests for Update- and UploadMtaExtDescriptor client methods
* Write tests for FileUpload and FileUploadToNode client methods
* Minor corrections
* Remove some TODO comments
* Rename some of response structures
* Revert change for line delimiters in cmd/piper.go
* Add uploadType string parameter to UploadFile and UploadRequest methods
of uploader mock to reflect the changed Uploader implementation
* Start to implement execution logic in tmsUpload.go
* Changes in tms.yaml file
- remove resources from inputs in tms.yaml and implement mtaPath
parameter settings in the yaml file the same way, as it is done in
cloudFoundryDeploy.yaml
- rename tms.yaml to tmsUpload.yaml, since some generation policy
changed meanwhile
* Rename tms.yaml to tmsUpload.yaml and do go generate
* Use provided proxy on communication with UAA and TMS
* Set proxy even before getting OAuth token
* Further implementation of tmsUpload.go
* Continuation on implementing the tmsUpload.go executor
* Get mtarFilePath and git commitId from commonPipelineEnvironment, if
they are missing in configuration file + minor changes
* Implement a happy path test for tmsUpload logic
* Cover with unit tests main happy and error paths of tmsUpload.go logic
* Extend set of unit tests for tmsUpload.go
* Eliminate some TODOs, extend unit tests for tmsUpload.go
* Delete some TODOs
* Remove a couple of more TODOs from tms_test.go file
* Provide additional unit test for error due unexpected positive http
status code on upload
* Revert back line delimiters in cmd/piper.go
* Comment out file uploading calls in tmsUpload.go
* Run go generate to update generated files
* Convert line delimiters in tmsUpload.yaml to Unix ones, as well as
provide new line character in the end of the file, plus minor fix for
logging in tmsUpload.go file (pipeline complained)
* Correct description of a parameter in tmsUpload.yaml, extend unit tests
to check for trimming a slash in the end of TMS url for client methods
that do upload
* [minor] Add a comment in the test code
* Add stashContent parameter to do unstashing in tmsUpload.groovy, remove
some of the clarified TODOs
* Uncomment uploading file calls in tmsUpload.go, declare buildResult
stash in tmsUpload.yaml
* Remove clarified TODOs from the tmsUpload.go file
* Run go fmt for jenkins-library/pkg/tms
* Do not get explicitly values from common pipeline environment - all
configurations are provided in yaml file
* Remove unused struct from tmsUpload_test.go
* Run go fmt jenkins-library\pkg\tms
* Revise descriptions of parameters provided in tmsUpload.yaml file
* Specify STAGES scope for tmsUpload parameters
* Provide STAGES scope for the tmsUpload parameters, provide default value
for stashContent parameter
* Remove trailing space from tmsUpload.yaml
* Provide unit tests for proxy-related changes in http.go file
* Improve proxy implementation in tmsUpload.go file
* Make tmsServiceKey again a mandatory parameter
* Run go generate command to make the generated files correspond the yaml
state
* Change line delimiters back to Unix ones (were switched while resolving
the conflicts)
* Remove trailing spaces from tmsUpload.yaml
* Minor change in a comment to trigger pipelines with commit
* Improve checks for zero-structs and for empty maps, as well as use
different package to read files in the tests
* Revert line endings in http.go
* Revert comments formatting changes in files that do not belong to the tmsUpload step
2022-08-30 10:16:09 +02:00
|
|
|
transportProxy *url.URL
|
2020-11-09 12:47:03 +02:00
|
|
|
username string
|
|
|
|
password string
|
|
|
|
token string
|
|
|
|
logger *logrus.Entry
|
|
|
|
cookieJar http.CookieJar
|
|
|
|
doLogRequestBodyOnDebug bool
|
|
|
|
doLogResponseBodyOnDebug bool
|
2021-02-04 15:58:35 +02:00
|
|
|
useDefaultTransport bool
|
2021-08-19 11:29:33 +02:00
|
|
|
trustedCerts []string
|
2022-03-23 11:02:00 +02:00
|
|
|
fileUtils piperutils.FileUtils
|
2019-12-09 18:35:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ClientOptions defines the options to be set on the client
|
|
|
|
type ClientOptions struct {
|
2020-03-23 16:02:22 +02:00
|
|
|
// MaxRequestDuration has a default value of "0", meaning "no maximum
|
|
|
|
// request duration". If it is greater than 0, an overall, hard timeout
|
|
|
|
// for the request will be enforced. This should only be used if the
|
|
|
|
// length of the request bodies is known.
|
|
|
|
MaxRequestDuration time.Duration
|
2020-11-11 14:35:53 +02:00
|
|
|
MaxRetries int
|
2020-06-10 11:14:55 +02:00
|
|
|
// TransportTimeout defaults to 3 minutes, if not specified. It is
|
2020-03-23 16:02:22 +02:00
|
|
|
// used for the transport layer and duration of handshakes and such.
|
2020-11-09 12:47:03 +02:00
|
|
|
TransportTimeout time.Duration
|
|
|
|
TransportSkipVerification bool
|
[TMS] Reimplement tmsUpload step in Go (#3399)
* Initially generated tmsUpload<...> files
* First provisioning of parameters supported by tmsUpload step
* Refer to Go step from tmsUpload.groovy
* Initial client implementation
* Reverting line delimiters in tmsUpoad.groovy back to Unix ones
* Temporarily remove when-condition for Release stage
* Define useGoStep parameter in tmsUpload.groovy
* Unstash buildResult if useGoStep is true
* No unstashing and empty credentials, when using go step
* Register TmsUploadCommand in piper.go
* Cleanup groovy-related changes - they will be temporarily implemented in a different repo
* Make getting OAuth token success
* Look through the code and cleanup it a bit
* Read service key from Jenkins credentials store
* Provide initial set of unit tests for methods in /pkg/tms/tms.go file
* Minor improvements on logging response on http call error
* Check, if positive HTTP status code is as expected
* Cleanup tms.yaml file, provide additional unit test for tms.go
* Provide unit test for the case, when request body contains spaces
* Specify nodeExtDescriptorMapping parameter as of type map in tms.yaml
* Implement client method for getting nodes
* Write tests for GetNodes method
* Add GetMtaExtDescriptor client method and cover it with unit tests
* Provide first implementation for Update- and UploadMtaExtDescriptor
client methods
* Provide first implementation for Update- and UploadMtaExtDescriptor
client methods
* Provide UploadFile and UploadFileToNode client methods
* Provide tests for Update- and UploadMtaExtDescriptor client methods
* Write tests for FileUpload and FileUploadToNode client methods
* Minor corrections
* Remove some TODO comments
* Rename some of response structures
* Revert change for line delimiters in cmd/piper.go
* Add uploadType string parameter to UploadFile and UploadRequest methods
of uploader mock to reflect the changed Uploader implementation
* Start to implement execution logic in tmsUpload.go
* Changes in tms.yaml file
- remove resources from inputs in tms.yaml and implement mtaPath
parameter settings in the yaml file the same way, as it is done in
cloudFoundryDeploy.yaml
- rename tms.yaml to tmsUpload.yaml, since some generation policy
changed meanwhile
* Rename tms.yaml to tmsUpload.yaml and do go generate
* Use provided proxy on communication with UAA and TMS
* Set proxy even before getting OAuth token
* Further implementation of tmsUpload.go
* Continuation on implementing the tmsUpload.go executor
* Get mtarFilePath and git commitId from commonPipelineEnvironment, if
they are missing in configuration file + minor changes
* Implement a happy path test for tmsUpload logic
* Cover with unit tests main happy and error paths of tmsUpload.go logic
* Extend set of unit tests for tmsUpload.go
* Eliminate some TODOs, extend unit tests for tmsUpload.go
* Delete some TODOs
* Remove a couple of more TODOs from tms_test.go file
* Provide additional unit test for error due unexpected positive http
status code on upload
* Revert back line delimiters in cmd/piper.go
* Comment out file uploading calls in tmsUpload.go
* Run go generate to update generated files
* Convert line delimiters in tmsUpload.yaml to Unix ones, as well as
provide new line character in the end of the file, plus minor fix for
logging in tmsUpload.go file (pipeline complained)
* Correct description of a parameter in tmsUpload.yaml, extend unit tests
to check for trimming a slash in the end of TMS url for client methods
that do upload
* [minor] Add a comment in the test code
* Add stashContent parameter to do unstashing in tmsUpload.groovy, remove
some of the clarified TODOs
* Uncomment uploading file calls in tmsUpload.go, declare buildResult
stash in tmsUpload.yaml
* Remove clarified TODOs from the tmsUpload.go file
* Run go fmt for jenkins-library/pkg/tms
* Do not get explicitly values from common pipeline environment - all
configurations are provided in yaml file
* Remove unused struct from tmsUpload_test.go
* Run go fmt jenkins-library\pkg\tms
* Revise descriptions of parameters provided in tmsUpload.yaml file
* Specify STAGES scope for tmsUpload parameters
* Provide STAGES scope for the tmsUpload parameters, provide default value
for stashContent parameter
* Remove trailing space from tmsUpload.yaml
* Provide unit tests for proxy-related changes in http.go file
* Improve proxy implementation in tmsUpload.go file
* Make tmsServiceKey again a mandatory parameter
* Run go generate command to make the generated files correspond the yaml
state
* Change line delimiters back to Unix ones (were switched while resolving
the conflicts)
* Remove trailing spaces from tmsUpload.yaml
* Minor change in a comment to trigger pipelines with commit
* Improve checks for zero-structs and for empty maps, as well as use
different package to read files in the tests
* Revert line endings in http.go
* Revert comments formatting changes in files that do not belong to the tmsUpload step
2022-08-30 10:16:09 +02:00
|
|
|
TransportProxy *url.URL
|
2020-11-09 12:47:03 +02:00
|
|
|
Username string
|
|
|
|
Password string
|
|
|
|
Token string
|
|
|
|
Logger *logrus.Entry
|
|
|
|
CookieJar http.CookieJar
|
|
|
|
DoLogRequestBodyOnDebug bool
|
|
|
|
DoLogResponseBodyOnDebug bool
|
2021-02-04 15:58:35 +02:00
|
|
|
UseDefaultTransport bool
|
2021-08-19 11:29:33 +02:00
|
|
|
TrustedCerts []string
|
2020-05-25 19:48:59 +02:00
|
|
|
}
|
|
|
|
|
2022-03-28 09:52:15 +02:00
|
|
|
// TransportWrapper is a wrapper for central round trip capabilities
|
2020-05-25 19:48:59 +02:00
|
|
|
type TransportWrapper struct {
|
|
|
|
Transport http.RoundTripper
|
|
|
|
doLogRequestBodyOnDebug bool
|
|
|
|
doLogResponseBodyOnDebug bool
|
2022-02-23 10:30:19 +02:00
|
|
|
username string
|
|
|
|
password string
|
|
|
|
token string
|
2020-05-25 19:48:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// UploadRequestData encapsulates the parameters for calling uploader.Upload()
|
|
|
|
type UploadRequestData struct {
|
|
|
|
// Method is the HTTP method used for the request. Must be one of http.MethodPost or http.MethodPut.
|
|
|
|
Method string
|
|
|
|
// URL for the request
|
|
|
|
URL string
|
|
|
|
// File path to be stored in the created form field.
|
|
|
|
File string
|
|
|
|
// Form field name under which the file name will be stored.
|
|
|
|
FileFieldName string
|
|
|
|
// Additional form fields which will be added to the request if not nil.
|
|
|
|
FormFields map[string]string
|
|
|
|
// Reader from which the file contents will be read.
|
|
|
|
FileContent io.Reader
|
|
|
|
Header http.Header
|
|
|
|
Cookies []*http.Cookie
|
2021-10-21 10:03:42 +02:00
|
|
|
UploadType string
|
2019-12-09 18:35:31 +02:00
|
|
|
}
|
|
|
|
|
2020-01-14 11:29:50 +02:00
|
|
|
// Sender provides an interface to the piper http client for uid/pwd and token authenticated requests
|
2019-12-09 18:35:31 +02:00
|
|
|
type Sender interface {
|
|
|
|
SendRequest(method, url string, body io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error)
|
|
|
|
SetOptions(options ClientOptions)
|
|
|
|
}
|
|
|
|
|
2020-01-14 11:29:50 +02:00
|
|
|
// Uploader provides an interface to the piper http client for uid/pwd and token authenticated requests with upload capabilities
|
|
|
|
type Uploader interface {
|
2020-03-23 16:02:22 +02:00
|
|
|
Sender
|
2021-10-21 10:03:42 +02:00
|
|
|
UploadRequest(method, url, file, fieldName string, header http.Header, cookies []*http.Cookie, uploadType string) (*http.Response, error)
|
|
|
|
UploadFile(url, file, fieldName string, header http.Header, cookies []*http.Cookie, uploadType string) (*http.Response, error)
|
2020-05-25 19:48:59 +02:00
|
|
|
Upload(data UploadRequestData) (*http.Response, error)
|
2020-01-14 11:29:50 +02:00
|
|
|
}
|
|
|
|
|
2022-03-23 11:02:00 +02:00
|
|
|
// fileUtils lazy initializes the utils
|
|
|
|
func (c *Client) getFileUtils() piperutils.FileUtils {
|
|
|
|
if c.fileUtils == nil {
|
|
|
|
c.fileUtils = &piperutils.Files{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.fileUtils
|
|
|
|
}
|
|
|
|
|
2020-01-14 11:29:50 +02:00
|
|
|
// UploadFile uploads a file's content as multipart-form POST request to the specified URL
|
2021-10-21 10:03:42 +02:00
|
|
|
func (c *Client) UploadFile(url, file, fileFieldName string, header http.Header, cookies []*http.Cookie, uploadType string) (*http.Response, error) {
|
|
|
|
return c.UploadRequest(http.MethodPost, url, file, fileFieldName, header, cookies, uploadType)
|
2020-01-22 15:22:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// UploadRequest uploads a file's content as multipart-form with given http method request to the specified URL
|
2021-10-21 10:03:42 +02:00
|
|
|
func (c *Client) UploadRequest(method, url, file, fileFieldName string, header http.Header, cookies []*http.Cookie, uploadType string) (*http.Response, error) {
|
2022-03-23 11:02:00 +02:00
|
|
|
fileHandle, err := c.getFileUtils().Open(file)
|
|
|
|
|
2020-05-25 19:48:59 +02:00
|
|
|
if err != nil {
|
|
|
|
return &http.Response{}, errors.Wrapf(err, "unable to locate file %v", file)
|
|
|
|
}
|
|
|
|
defer fileHandle.Close()
|
2021-10-21 10:03:42 +02:00
|
|
|
|
2020-05-25 19:48:59 +02:00
|
|
|
return c.Upload(UploadRequestData{
|
|
|
|
Method: method,
|
|
|
|
URL: url,
|
|
|
|
File: file,
|
|
|
|
FileFieldName: fileFieldName,
|
|
|
|
FileContent: fileHandle,
|
|
|
|
Header: header,
|
|
|
|
Cookies: cookies,
|
2021-10-21 10:03:42 +02:00
|
|
|
UploadType: uploadType,
|
2020-05-25 19:48:59 +02:00
|
|
|
})
|
|
|
|
}
|
2020-01-22 16:10:40 +02:00
|
|
|
|
2021-10-21 10:03:42 +02:00
|
|
|
// Upload uploads a file's content as multipart-form or pure binary with given http method request to the specified URL
|
2020-05-25 19:48:59 +02:00
|
|
|
func (c *Client) Upload(data UploadRequestData) (*http.Response, error) {
|
|
|
|
if data.Method != http.MethodPost && data.Method != http.MethodPut {
|
|
|
|
return nil, errors.New(fmt.Sprintf("Http method %v is not allowed. Possible values are %v or %v", data.Method, http.MethodPost, http.MethodPut))
|
2020-01-22 16:10:40 +02:00
|
|
|
}
|
|
|
|
|
2021-10-21 10:03:42 +02:00
|
|
|
// Binary upload :: other options ("binary" or "form").
|
|
|
|
if data.UploadType == "binary" {
|
|
|
|
request, err := c.createRequest(data.Method, data.URL, data.FileContent, &data.Header, data.Cookies)
|
|
|
|
if err != nil {
|
|
|
|
c.logger.Debugf("New %v request to %v (binary upload)", data.Method, data.URL)
|
|
|
|
return &http.Response{}, errors.Wrapf(err, "error creating %v request to %v (binary upload)", data.Method, data.URL)
|
|
|
|
}
|
|
|
|
request.Header.Add("Content-Type", "application/octet-stream")
|
|
|
|
request.Header.Add("Connection", "Keep-Alive")
|
|
|
|
|
|
|
|
return c.Send(request)
|
|
|
|
|
|
|
|
} else { // For form upload
|
|
|
|
|
|
|
|
bodyBuffer := &bytes.Buffer{}
|
|
|
|
bodyWriter := multipart.NewWriter(bodyBuffer)
|
2020-01-14 11:29:50 +02:00
|
|
|
|
2021-10-21 10:03:42 +02:00
|
|
|
if data.FormFields != nil {
|
|
|
|
for fieldName, fieldValue := range data.FormFields {
|
|
|
|
err := bodyWriter.WriteField(fieldName, fieldValue)
|
|
|
|
if err != nil {
|
|
|
|
return &http.Response{}, errors.Wrapf(err, "error writing form field %v with value %v", fieldName, fieldValue)
|
|
|
|
}
|
2020-05-25 19:48:59 +02:00
|
|
|
}
|
|
|
|
}
|
2020-01-14 11:29:50 +02:00
|
|
|
|
2021-10-21 10:03:42 +02:00
|
|
|
fileWriter, err := bodyWriter.CreateFormFile(data.FileFieldName, data.File)
|
|
|
|
if err != nil {
|
|
|
|
return &http.Response{}, errors.Wrapf(err, "error creating form file %v for field %v", data.File, data.FileFieldName)
|
|
|
|
}
|
2020-01-14 11:29:50 +02:00
|
|
|
|
2021-10-21 10:03:42 +02:00
|
|
|
_, err = piperutils.CopyData(fileWriter, data.FileContent)
|
|
|
|
if err != nil {
|
|
|
|
return &http.Response{}, errors.Wrapf(err, "unable to copy file content of %v into request body", data.File)
|
|
|
|
}
|
|
|
|
err = bodyWriter.Close()
|
2022-02-23 10:30:19 +02:00
|
|
|
if err != nil {
|
|
|
|
log.Entry().Warn("failed to close writer on request body")
|
|
|
|
}
|
2020-01-14 11:29:50 +02:00
|
|
|
|
2021-10-21 10:03:42 +02:00
|
|
|
request, err := c.createRequest(data.Method, data.URL, bodyBuffer, &data.Header, data.Cookies)
|
|
|
|
if err != nil {
|
2022-02-23 10:30:19 +02:00
|
|
|
c.logger.Debugf("new %v request to %v", data.Method, data.URL)
|
2021-10-21 10:03:42 +02:00
|
|
|
return &http.Response{}, errors.Wrapf(err, "error creating %v request to %v", data.Method, data.URL)
|
|
|
|
}
|
2020-01-14 11:29:50 +02:00
|
|
|
|
2021-10-21 10:03:42 +02:00
|
|
|
startBoundary := strings.Index(bodyWriter.FormDataContentType(), "=") + 1
|
|
|
|
boundary := bodyWriter.FormDataContentType()[startBoundary:]
|
|
|
|
request.Header.Add("Content-Type", "multipart/form-data; boundary=\""+boundary+"\"")
|
|
|
|
request.Header.Add("Connection", "Keep-Alive")
|
2020-01-14 11:29:50 +02:00
|
|
|
|
2021-10-21 10:03:42 +02:00
|
|
|
return c.Send(request)
|
|
|
|
}
|
2020-01-14 11:29:50 +02:00
|
|
|
}
|
|
|
|
|
2022-03-28 09:52:15 +02:00
|
|
|
// SendRequest sends a http request with a defined method
|
2021-01-12 16:26:45 +02:00
|
|
|
//
|
|
|
|
// On error, any Response can be ignored and the Response.Body
|
|
|
|
// does not need to be closed.
|
2019-12-09 18:35:31 +02:00
|
|
|
func (c *Client) SendRequest(method, url string, body io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error) {
|
2020-01-14 11:29:50 +02:00
|
|
|
request, err := c.createRequest(method, url, body, &header, cookies)
|
|
|
|
if err != nil {
|
|
|
|
return &http.Response{}, errors.Wrapf(err, "error creating %v request to %v", method, url)
|
|
|
|
}
|
2019-12-09 18:35:31 +02:00
|
|
|
|
2021-02-08 15:26:15 +02:00
|
|
|
return c.Send(request)
|
|
|
|
}
|
|
|
|
|
2022-03-28 09:52:15 +02:00
|
|
|
// Send sends a http request
|
2021-02-08 15:26:15 +02:00
|
|
|
func (c *Client) Send(request *http.Request) (*http.Response, error) {
|
|
|
|
httpClient := c.initialize()
|
2020-01-14 11:29:50 +02:00
|
|
|
response, err := httpClient.Do(request)
|
|
|
|
if err != nil {
|
2021-02-08 15:26:15 +02:00
|
|
|
return response, errors.Wrapf(err, "HTTP %v request to %v failed", request.Method, request.URL)
|
2020-01-14 11:29:50 +02:00
|
|
|
}
|
2021-02-08 15:26:15 +02:00
|
|
|
return c.handleResponse(response, request.URL.String())
|
2020-01-14 11:29:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetOptions sets options used for the http client
|
|
|
|
func (c *Client) SetOptions(options ClientOptions) {
|
2020-05-25 19:48:59 +02:00
|
|
|
c.doLogRequestBodyOnDebug = options.DoLogRequestBodyOnDebug
|
|
|
|
c.doLogResponseBodyOnDebug = options.DoLogResponseBodyOnDebug
|
2021-02-04 15:58:35 +02:00
|
|
|
c.useDefaultTransport = options.UseDefaultTransport
|
2020-03-23 16:02:22 +02:00
|
|
|
c.transportTimeout = options.TransportTimeout
|
2020-11-09 12:47:03 +02:00
|
|
|
c.transportSkipVerification = options.TransportSkipVerification
|
[TMS] Reimplement tmsUpload step in Go (#3399)
* Initially generated tmsUpload<...> files
* First provisioning of parameters supported by tmsUpload step
* Refer to Go step from tmsUpload.groovy
* Initial client implementation
* Reverting line delimiters in tmsUpoad.groovy back to Unix ones
* Temporarily remove when-condition for Release stage
* Define useGoStep parameter in tmsUpload.groovy
* Unstash buildResult if useGoStep is true
* No unstashing and empty credentials, when using go step
* Register TmsUploadCommand in piper.go
* Cleanup groovy-related changes - they will be temporarily implemented in a different repo
* Make getting OAuth token success
* Look through the code and cleanup it a bit
* Read service key from Jenkins credentials store
* Provide initial set of unit tests for methods in /pkg/tms/tms.go file
* Minor improvements on logging response on http call error
* Check, if positive HTTP status code is as expected
* Cleanup tms.yaml file, provide additional unit test for tms.go
* Provide unit test for the case, when request body contains spaces
* Specify nodeExtDescriptorMapping parameter as of type map in tms.yaml
* Implement client method for getting nodes
* Write tests for GetNodes method
* Add GetMtaExtDescriptor client method and cover it with unit tests
* Provide first implementation for Update- and UploadMtaExtDescriptor
client methods
* Provide first implementation for Update- and UploadMtaExtDescriptor
client methods
* Provide UploadFile and UploadFileToNode client methods
* Provide tests for Update- and UploadMtaExtDescriptor client methods
* Write tests for FileUpload and FileUploadToNode client methods
* Minor corrections
* Remove some TODO comments
* Rename some of response structures
* Revert change for line delimiters in cmd/piper.go
* Add uploadType string parameter to UploadFile and UploadRequest methods
of uploader mock to reflect the changed Uploader implementation
* Start to implement execution logic in tmsUpload.go
* Changes in tms.yaml file
- remove resources from inputs in tms.yaml and implement mtaPath
parameter settings in the yaml file the same way, as it is done in
cloudFoundryDeploy.yaml
- rename tms.yaml to tmsUpload.yaml, since some generation policy
changed meanwhile
* Rename tms.yaml to tmsUpload.yaml and do go generate
* Use provided proxy on communication with UAA and TMS
* Set proxy even before getting OAuth token
* Further implementation of tmsUpload.go
* Continuation on implementing the tmsUpload.go executor
* Get mtarFilePath and git commitId from commonPipelineEnvironment, if
they are missing in configuration file + minor changes
* Implement a happy path test for tmsUpload logic
* Cover with unit tests main happy and error paths of tmsUpload.go logic
* Extend set of unit tests for tmsUpload.go
* Eliminate some TODOs, extend unit tests for tmsUpload.go
* Delete some TODOs
* Remove a couple of more TODOs from tms_test.go file
* Provide additional unit test for error due unexpected positive http
status code on upload
* Revert back line delimiters in cmd/piper.go
* Comment out file uploading calls in tmsUpload.go
* Run go generate to update generated files
* Convert line delimiters in tmsUpload.yaml to Unix ones, as well as
provide new line character in the end of the file, plus minor fix for
logging in tmsUpload.go file (pipeline complained)
* Correct description of a parameter in tmsUpload.yaml, extend unit tests
to check for trimming a slash in the end of TMS url for client methods
that do upload
* [minor] Add a comment in the test code
* Add stashContent parameter to do unstashing in tmsUpload.groovy, remove
some of the clarified TODOs
* Uncomment uploading file calls in tmsUpload.go, declare buildResult
stash in tmsUpload.yaml
* Remove clarified TODOs from the tmsUpload.go file
* Run go fmt for jenkins-library/pkg/tms
* Do not get explicitly values from common pipeline environment - all
configurations are provided in yaml file
* Remove unused struct from tmsUpload_test.go
* Run go fmt jenkins-library\pkg\tms
* Revise descriptions of parameters provided in tmsUpload.yaml file
* Specify STAGES scope for tmsUpload parameters
* Provide STAGES scope for the tmsUpload parameters, provide default value
for stashContent parameter
* Remove trailing space from tmsUpload.yaml
* Provide unit tests for proxy-related changes in http.go file
* Improve proxy implementation in tmsUpload.go file
* Make tmsServiceKey again a mandatory parameter
* Run go generate command to make the generated files correspond the yaml
state
* Change line delimiters back to Unix ones (were switched while resolving
the conflicts)
* Remove trailing spaces from tmsUpload.yaml
* Minor change in a comment to trigger pipelines with commit
* Improve checks for zero-structs and for empty maps, as well as use
different package to read files in the tests
* Revert line endings in http.go
* Revert comments formatting changes in files that do not belong to the tmsUpload step
2022-08-30 10:16:09 +02:00
|
|
|
c.transportProxy = options.TransportProxy
|
2020-03-23 16:02:22 +02:00
|
|
|
c.maxRequestDuration = options.MaxRequestDuration
|
2020-01-14 11:29:50 +02:00
|
|
|
c.username = options.Username
|
|
|
|
c.password = options.Password
|
|
|
|
c.token = options.Token
|
2021-06-15 11:13:24 +02:00
|
|
|
if options.MaxRetries < 0 {
|
|
|
|
c.maxRetries = 0
|
|
|
|
} else if options.MaxRetries == 0 {
|
|
|
|
c.maxRetries = 15
|
|
|
|
} else {
|
|
|
|
c.maxRetries = options.MaxRetries
|
|
|
|
}
|
2020-02-06 17:16:34 +02:00
|
|
|
|
|
|
|
if options.Logger != nil {
|
|
|
|
c.logger = options.Logger
|
|
|
|
} else {
|
|
|
|
c.logger = log.Entry().WithField("package", "SAP/jenkins-library/pkg/http")
|
|
|
|
}
|
2020-02-04 12:43:27 +02:00
|
|
|
c.cookieJar = options.CookieJar
|
2021-08-19 11:29:33 +02:00
|
|
|
c.trustedCerts = options.TrustedCerts
|
2022-03-23 11:02:00 +02:00
|
|
|
c.fileUtils = &piperutils.Files{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetFileUtils can be used to overwrite the default file utils
|
|
|
|
func (c *Client) SetFileUtils(fileUtils piperutils.FileUtils) {
|
|
|
|
c.fileUtils = fileUtils
|
2020-01-14 11:29:50 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 12:13:19 +02:00
|
|
|
// StandardClient returns a stdlib *http.Client which respects the custom settings.
|
|
|
|
func (c *Client) StandardClient() *http.Client {
|
|
|
|
return c.initialize()
|
|
|
|
}
|
|
|
|
|
2020-01-14 11:29:50 +02:00
|
|
|
func (c *Client) initialize() *http.Client {
|
2019-12-09 18:35:31 +02:00
|
|
|
c.applyDefaults()
|
2020-01-29 14:17:54 +02:00
|
|
|
c.logger = log.Entry().WithField("package", "SAP/jenkins-library/pkg/http")
|
2019-12-09 18:35:31 +02:00
|
|
|
|
2020-05-25 19:48:59 +02:00
|
|
|
var transport = &TransportWrapper{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
DialContext: (&net.Dialer{
|
|
|
|
Timeout: c.transportTimeout,
|
|
|
|
}).DialContext,
|
[TMS] Reimplement tmsUpload step in Go (#3399)
* Initially generated tmsUpload<...> files
* First provisioning of parameters supported by tmsUpload step
* Refer to Go step from tmsUpload.groovy
* Initial client implementation
* Reverting line delimiters in tmsUpoad.groovy back to Unix ones
* Temporarily remove when-condition for Release stage
* Define useGoStep parameter in tmsUpload.groovy
* Unstash buildResult if useGoStep is true
* No unstashing and empty credentials, when using go step
* Register TmsUploadCommand in piper.go
* Cleanup groovy-related changes - they will be temporarily implemented in a different repo
* Make getting OAuth token success
* Look through the code and cleanup it a bit
* Read service key from Jenkins credentials store
* Provide initial set of unit tests for methods in /pkg/tms/tms.go file
* Minor improvements on logging response on http call error
* Check, if positive HTTP status code is as expected
* Cleanup tms.yaml file, provide additional unit test for tms.go
* Provide unit test for the case, when request body contains spaces
* Specify nodeExtDescriptorMapping parameter as of type map in tms.yaml
* Implement client method for getting nodes
* Write tests for GetNodes method
* Add GetMtaExtDescriptor client method and cover it with unit tests
* Provide first implementation for Update- and UploadMtaExtDescriptor
client methods
* Provide first implementation for Update- and UploadMtaExtDescriptor
client methods
* Provide UploadFile and UploadFileToNode client methods
* Provide tests for Update- and UploadMtaExtDescriptor client methods
* Write tests for FileUpload and FileUploadToNode client methods
* Minor corrections
* Remove some TODO comments
* Rename some of response structures
* Revert change for line delimiters in cmd/piper.go
* Add uploadType string parameter to UploadFile and UploadRequest methods
of uploader mock to reflect the changed Uploader implementation
* Start to implement execution logic in tmsUpload.go
* Changes in tms.yaml file
- remove resources from inputs in tms.yaml and implement mtaPath
parameter settings in the yaml file the same way, as it is done in
cloudFoundryDeploy.yaml
- rename tms.yaml to tmsUpload.yaml, since some generation policy
changed meanwhile
* Rename tms.yaml to tmsUpload.yaml and do go generate
* Use provided proxy on communication with UAA and TMS
* Set proxy even before getting OAuth token
* Further implementation of tmsUpload.go
* Continuation on implementing the tmsUpload.go executor
* Get mtarFilePath and git commitId from commonPipelineEnvironment, if
they are missing in configuration file + minor changes
* Implement a happy path test for tmsUpload logic
* Cover with unit tests main happy and error paths of tmsUpload.go logic
* Extend set of unit tests for tmsUpload.go
* Eliminate some TODOs, extend unit tests for tmsUpload.go
* Delete some TODOs
* Remove a couple of more TODOs from tms_test.go file
* Provide additional unit test for error due unexpected positive http
status code on upload
* Revert back line delimiters in cmd/piper.go
* Comment out file uploading calls in tmsUpload.go
* Run go generate to update generated files
* Convert line delimiters in tmsUpload.yaml to Unix ones, as well as
provide new line character in the end of the file, plus minor fix for
logging in tmsUpload.go file (pipeline complained)
* Correct description of a parameter in tmsUpload.yaml, extend unit tests
to check for trimming a slash in the end of TMS url for client methods
that do upload
* [minor] Add a comment in the test code
* Add stashContent parameter to do unstashing in tmsUpload.groovy, remove
some of the clarified TODOs
* Uncomment uploading file calls in tmsUpload.go, declare buildResult
stash in tmsUpload.yaml
* Remove clarified TODOs from the tmsUpload.go file
* Run go fmt for jenkins-library/pkg/tms
* Do not get explicitly values from common pipeline environment - all
configurations are provided in yaml file
* Remove unused struct from tmsUpload_test.go
* Run go fmt jenkins-library\pkg\tms
* Revise descriptions of parameters provided in tmsUpload.yaml file
* Specify STAGES scope for tmsUpload parameters
* Provide STAGES scope for the tmsUpload parameters, provide default value
for stashContent parameter
* Remove trailing space from tmsUpload.yaml
* Provide unit tests for proxy-related changes in http.go file
* Improve proxy implementation in tmsUpload.go file
* Make tmsServiceKey again a mandatory parameter
* Run go generate command to make the generated files correspond the yaml
state
* Change line delimiters back to Unix ones (were switched while resolving
the conflicts)
* Remove trailing spaces from tmsUpload.yaml
* Minor change in a comment to trigger pipelines with commit
* Improve checks for zero-structs and for empty maps, as well as use
different package to read files in the tests
* Revert line endings in http.go
* Revert comments formatting changes in files that do not belong to the tmsUpload step
2022-08-30 10:16:09 +02:00
|
|
|
Proxy: http.ProxyURL(c.transportProxy),
|
2020-05-25 19:48:59 +02:00
|
|
|
ResponseHeaderTimeout: c.transportTimeout,
|
|
|
|
ExpectContinueTimeout: c.transportTimeout,
|
|
|
|
TLSHandshakeTimeout: c.transportTimeout,
|
2020-11-09 12:47:03 +02:00
|
|
|
TLSClientConfig: &tls.Config{
|
|
|
|
InsecureSkipVerify: c.transportSkipVerification,
|
|
|
|
},
|
2020-05-25 19:48:59 +02:00
|
|
|
},
|
|
|
|
doLogRequestBodyOnDebug: c.doLogRequestBodyOnDebug,
|
|
|
|
doLogResponseBodyOnDebug: c.doLogResponseBodyOnDebug,
|
2022-02-23 10:30:19 +02:00
|
|
|
token: c.token,
|
|
|
|
username: c.username,
|
|
|
|
password: c.password,
|
2020-03-23 16:02:22 +02:00
|
|
|
}
|
2020-11-11 14:35:53 +02:00
|
|
|
|
2022-02-23 10:30:19 +02:00
|
|
|
if len(c.trustedCerts) > 0 && !c.useDefaultTransport && !c.transportSkipVerification {
|
2022-11-10 14:17:21 +02:00
|
|
|
log.Entry().Debug("adding certs for tls to trust")
|
2021-08-19 11:29:33 +02:00
|
|
|
err := c.configureTLSToTrustCertificates(transport)
|
|
|
|
if err != nil {
|
2022-02-23 10:30:19 +02:00
|
|
|
log.Entry().Infof("adding certs for tls config failed : %v, continuing with the existing tsl config", err)
|
2021-08-19 11:29:33 +02:00
|
|
|
}
|
|
|
|
} else {
|
2021-09-23 10:05:44 +02:00
|
|
|
log.Entry().Debug("no trusted certs found / using default transport / insecure skip set to true / : continuing with existing tls config")
|
2021-08-19 11:29:33 +02:00
|
|
|
}
|
|
|
|
|
2020-11-11 14:35:53 +02:00
|
|
|
var httpClient *http.Client
|
|
|
|
if c.maxRetries > 0 {
|
|
|
|
retryClient := retryablehttp.NewClient()
|
2021-06-15 11:13:24 +02:00
|
|
|
localLogger := log.Entry()
|
|
|
|
localLogger.Level = logrus.DebugLevel
|
|
|
|
retryClient.Logger = localLogger
|
2020-11-11 14:35:53 +02:00
|
|
|
retryClient.HTTPClient.Timeout = c.maxRequestDuration
|
|
|
|
retryClient.HTTPClient.Jar = c.cookieJar
|
|
|
|
retryClient.RetryMax = c.maxRetries
|
2021-02-04 15:58:35 +02:00
|
|
|
if !c.useDefaultTransport {
|
|
|
|
retryClient.HTTPClient.Transport = transport
|
2022-02-23 10:30:19 +02:00
|
|
|
} else {
|
|
|
|
retryClient.HTTPClient.Transport = &TransportWrapper{
|
|
|
|
Transport: retryClient.HTTPClient.Transport,
|
|
|
|
doLogRequestBodyOnDebug: c.doLogRequestBodyOnDebug,
|
|
|
|
doLogResponseBodyOnDebug: c.doLogResponseBodyOnDebug,
|
|
|
|
token: c.token,
|
|
|
|
username: c.username,
|
|
|
|
password: c.password}
|
2021-02-04 15:58:35 +02:00
|
|
|
}
|
2021-03-09 14:41:07 +02:00
|
|
|
retryClient.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) {
|
2021-06-15 11:13:24 +02:00
|
|
|
if err != nil && (strings.Contains(err.Error(), "timeout") || strings.Contains(err.Error(), "timed out") || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "connection reset")) {
|
|
|
|
// Assuming timeouts, resets, and similar could be retried
|
2021-03-09 14:41:07 +02:00
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
return retryablehttp.DefaultRetryPolicy(ctx, resp, err)
|
|
|
|
}
|
2020-11-11 14:35:53 +02:00
|
|
|
httpClient = retryClient.StandardClient()
|
|
|
|
} else {
|
|
|
|
httpClient = &http.Client{}
|
|
|
|
httpClient.Timeout = c.maxRequestDuration
|
|
|
|
httpClient.Jar = c.cookieJar
|
2021-02-04 15:58:35 +02:00
|
|
|
if !c.useDefaultTransport {
|
|
|
|
httpClient.Transport = transport
|
|
|
|
}
|
2019-12-09 18:35:31 +02:00
|
|
|
}
|
2020-11-11 14:35:53 +02:00
|
|
|
|
2020-11-09 12:47:03 +02:00
|
|
|
if c.transportSkipVerification {
|
|
|
|
c.logger.Debugf("TLS verification disabled")
|
|
|
|
}
|
2020-11-11 14:35:53 +02:00
|
|
|
|
2020-03-23 16:02:22 +02:00
|
|
|
c.logger.Debugf("Transport timeout: %v, max request duration: %v", c.transportTimeout, c.maxRequestDuration)
|
2020-01-14 11:29:50 +02:00
|
|
|
|
|
|
|
return httpClient
|
|
|
|
}
|
2019-12-09 18:35:31 +02:00
|
|
|
|
2020-05-25 19:48:59 +02:00
|
|
|
type contextKey struct {
|
|
|
|
name string
|
|
|
|
}
|
|
|
|
|
|
|
|
var contextKeyRequestStart = &contextKey{"RequestStart"}
|
2022-02-23 10:30:19 +02:00
|
|
|
var authHeaderKey = "Authorization"
|
2020-05-25 19:48:59 +02:00
|
|
|
|
|
|
|
// RoundTrip is the core part of this module and implements http.RoundTripper.
|
2022-03-28 09:52:15 +02:00
|
|
|
// Executes HTTP requests with request/response logging.
|
2020-05-25 19:48:59 +02:00
|
|
|
func (t *TransportWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
|
|
ctx := context.WithValue(req.Context(), contextKeyRequestStart, time.Now())
|
|
|
|
req = req.WithContext(ctx)
|
|
|
|
|
2022-02-23 10:30:19 +02:00
|
|
|
handleAuthentication(req, t.username, t.password, t.token)
|
|
|
|
|
2020-05-25 19:48:59 +02:00
|
|
|
t.logRequest(req)
|
2022-02-23 10:30:19 +02:00
|
|
|
|
2020-05-25 19:48:59 +02:00
|
|
|
resp, err := t.Transport.RoundTrip(req)
|
2022-02-23 10:30:19 +02:00
|
|
|
|
2020-05-25 19:48:59 +02:00
|
|
|
t.logResponse(resp)
|
|
|
|
|
|
|
|
return resp, err
|
|
|
|
}
|
|
|
|
|
2022-02-23 10:30:19 +02:00
|
|
|
func handleAuthentication(req *http.Request, username, password, token string) {
|
2022-03-28 09:52:15 +02:00
|
|
|
// Handle authentication if not done already
|
2022-02-23 10:30:19 +02:00
|
|
|
if (len(username) > 0 || len(password) > 0) && len(req.Header.Get(authHeaderKey)) == 0 {
|
|
|
|
req.SetBasicAuth(username, password)
|
2022-10-10 10:55:21 +02:00
|
|
|
log.Entry().Debug("Using Basic Authentication ****/****\n")
|
2022-02-23 10:30:19 +02:00
|
|
|
}
|
|
|
|
if len(token) > 0 && len(req.Header.Get(authHeaderKey)) == 0 {
|
|
|
|
req.Header.Add(authHeaderKey, token)
|
|
|
|
log.Entry().Debug("Using Token Authentication ****")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-25 19:48:59 +02:00
|
|
|
func (t *TransportWrapper) logRequest(req *http.Request) {
|
|
|
|
log.Entry().Debug("--------------------------------")
|
|
|
|
log.Entry().Debugf("--> %v request to %v", req.Method, req.URL)
|
2021-01-04 11:06:28 +02:00
|
|
|
log.Entry().Debugf("headers: %v", transformHeaders(req.Header))
|
2020-05-25 19:48:59 +02:00
|
|
|
log.Entry().Debugf("cookies: %v", transformCookies(req.Cookies()))
|
2022-02-23 10:30:19 +02:00
|
|
|
if t.doLogRequestBodyOnDebug && req.Body != nil {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
tee := io.TeeReader(req.Body, &buf)
|
|
|
|
log.Entry().Debugf("body: %v", transformBody(tee))
|
|
|
|
req.Body = ioutil.NopCloser(bytes.NewReader(buf.Bytes()))
|
|
|
|
log.Entry().Debugf("body: %v", transformBody(tee))
|
2020-05-25 19:48:59 +02:00
|
|
|
}
|
|
|
|
log.Entry().Debug("--------------------------------")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TransportWrapper) logResponse(resp *http.Response) {
|
|
|
|
if resp != nil {
|
|
|
|
ctx := resp.Request.Context()
|
|
|
|
if start, ok := ctx.Value(contextKeyRequestStart).(time.Time); ok {
|
|
|
|
log.Entry().Debugf("<-- response %v %v (%v)", resp.StatusCode, resp.Request.URL, roundtime.Duration(time.Now().Sub(start), 2))
|
|
|
|
} else {
|
|
|
|
log.Entry().Debugf("<-- response %v %v", resp.StatusCode, resp.Request.URL)
|
|
|
|
}
|
2022-02-23 10:30:19 +02:00
|
|
|
if t.doLogResponseBodyOnDebug && resp.Body != nil {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
tee := io.TeeReader(resp.Body, &buf)
|
|
|
|
log.Entry().Debugf("body: %v", transformBody(tee))
|
|
|
|
resp.Body = ioutil.NopCloser(bytes.NewReader(buf.Bytes()))
|
2020-05-25 19:48:59 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.Entry().Debug("response <nil>")
|
|
|
|
}
|
|
|
|
log.Entry().Debug("--------------------------------")
|
|
|
|
}
|
|
|
|
|
2021-01-04 11:06:28 +02:00
|
|
|
func transformHeaders(header http.Header) http.Header {
|
|
|
|
var h http.Header = map[string][]string{}
|
|
|
|
for name, value := range header {
|
|
|
|
if name == "Authorization" {
|
|
|
|
for _, v := range value {
|
|
|
|
// The format of the Authorization header value is: <type> <cred>.
|
|
|
|
// We don't register the full string since only the part after
|
|
|
|
// the first token is the secret in the narrower sense (applies at
|
|
|
|
// least for basic auth)
|
|
|
|
log.RegisterSecret(strings.Join(strings.Split(v, " ")[1:], " "))
|
|
|
|
}
|
|
|
|
// Since
|
|
|
|
// 1.) The auth header type itself might serve as a vector for an
|
|
|
|
// intrusion
|
2022-03-28 09:52:15 +02:00
|
|
|
// 2.) We cannot make assumptions about the structure of the auth
|
2021-01-04 11:06:28 +02:00
|
|
|
// header value since that depends on the type, e.g. several tokens
|
2022-03-28 09:52:15 +02:00
|
|
|
// where only some tokens define the secret
|
2021-01-04 11:06:28 +02:00
|
|
|
// we hide the full auth header value anyway in order to be on the
|
|
|
|
// save side.
|
|
|
|
value = []string{"<set>"}
|
|
|
|
}
|
|
|
|
h[name] = value
|
|
|
|
}
|
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
2020-05-25 19:48:59 +02:00
|
|
|
func transformCookies(cookies []*http.Cookie) string {
|
|
|
|
result := ""
|
|
|
|
for _, c := range cookies {
|
|
|
|
result = fmt.Sprintf("%v %v", result, c.String())
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2022-02-23 10:30:19 +02:00
|
|
|
func transformBody(body io.Reader) string {
|
2020-05-25 19:48:59 +02:00
|
|
|
if body == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
buf.ReadFrom(body)
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
2020-01-14 11:29:50 +02:00
|
|
|
func (c *Client) createRequest(method, url string, body io.Reader, header *http.Header, cookies []*http.Cookie) (*http.Request, error) {
|
2019-12-09 18:35:31 +02:00
|
|
|
request, err := http.NewRequest(method, url, body)
|
|
|
|
if err != nil {
|
2020-01-14 11:29:50 +02:00
|
|
|
return &http.Request{}, err
|
2019-12-09 18:35:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if header != nil {
|
2020-01-14 11:29:50 +02:00
|
|
|
for name, headers := range *header {
|
2019-12-09 18:35:31 +02:00
|
|
|
for _, h := range headers {
|
|
|
|
request.Header.Add(name, h)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-23 10:30:19 +02:00
|
|
|
handleAuthentication(request, c.username, c.password, c.token)
|
2019-12-09 18:35:31 +02:00
|
|
|
|
2022-02-23 10:30:19 +02:00
|
|
|
for _, cookie := range cookies {
|
|
|
|
request.AddCookie(cookie)
|
2019-12-09 18:35:31 +02:00
|
|
|
}
|
|
|
|
|
2020-01-14 11:29:50 +02:00
|
|
|
return request, nil
|
|
|
|
}
|
|
|
|
|
2020-12-16 09:55:22 +02:00
|
|
|
func (c *Client) handleResponse(response *http.Response, url string) (*http.Response, error) {
|
2019-12-09 18:35:31 +02:00
|
|
|
// 2xx codes do not create an error
|
|
|
|
if response.StatusCode >= 200 && response.StatusCode < 300 {
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
switch response.StatusCode {
|
|
|
|
case http.StatusUnauthorized:
|
2020-01-14 11:29:50 +02:00
|
|
|
c.logger.WithField("HTTP Error", "401 (Unauthorized)").Error("Credentials invalid, please check your user credentials!")
|
2019-12-09 18:35:31 +02:00
|
|
|
case http.StatusForbidden:
|
2020-01-14 11:29:50 +02:00
|
|
|
c.logger.WithField("HTTP Error", "403 (Forbidden)").Error("Permission issue, please check your user permissions!")
|
2019-12-09 18:35:31 +02:00
|
|
|
case http.StatusNotFound:
|
2020-12-16 09:55:22 +02:00
|
|
|
c.logger.WithField("HTTP Error", "404 (Not Found)").Errorf("Requested resource ('%s') could not be found", url)
|
2019-12-09 18:35:31 +02:00
|
|
|
case http.StatusInternalServerError:
|
2020-05-25 19:48:59 +02:00
|
|
|
c.logger.WithField("HTTP Error", "500 (Internal Server Error)").Error("Unknown error occurred.")
|
2019-12-09 18:35:31 +02:00
|
|
|
}
|
|
|
|
|
2021-08-19 11:29:33 +02:00
|
|
|
return response, fmt.Errorf("request to %v returned with response %v", response.Request.URL, response.Status)
|
2019-12-09 18:35:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) applyDefaults() {
|
2020-03-23 16:02:22 +02:00
|
|
|
if c.transportTimeout == 0 {
|
2020-06-10 11:14:55 +02:00
|
|
|
c.transportTimeout = 3 * time.Minute
|
2019-12-09 18:35:31 +02:00
|
|
|
}
|
2020-01-28 00:40:53 +02:00
|
|
|
if c.logger == nil {
|
|
|
|
c.logger = log.Entry().WithField("package", "SAP/jenkins-library/pkg/http")
|
|
|
|
}
|
2019-12-09 18:35:31 +02:00
|
|
|
}
|
2020-07-14 10:58:57 +02:00
|
|
|
|
2021-08-19 11:29:33 +02:00
|
|
|
func (c *Client) configureTLSToTrustCertificates(transport *TransportWrapper) error {
|
|
|
|
|
|
|
|
trustStoreDir, err := getWorkingDirForTrustStore()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to create trust store directory")
|
|
|
|
}
|
|
|
|
/* insecure := flag.Bool("insecure-ssl", false, "Accept/Ignore all server SSL certificates") */
|
2022-02-23 10:30:19 +02:00
|
|
|
// Get the SystemCertPool, continue with an empty pool on error
|
|
|
|
rootCAs, err := x509.SystemCertPool()
|
|
|
|
if err != nil {
|
|
|
|
log.Entry().Debugf("Caught error on store lookup %v", err)
|
|
|
|
}
|
2021-08-19 11:29:33 +02:00
|
|
|
|
2022-02-23 10:30:19 +02:00
|
|
|
if rootCAs == nil {
|
|
|
|
rootCAs = x509.NewCertPool()
|
|
|
|
}
|
2021-08-19 11:29:33 +02:00
|
|
|
|
2022-02-23 10:30:19 +02:00
|
|
|
*transport = TransportWrapper{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
DialContext: (&net.Dialer{
|
|
|
|
Timeout: c.transportTimeout,
|
|
|
|
}).DialContext,
|
|
|
|
ResponseHeaderTimeout: c.transportTimeout,
|
|
|
|
ExpectContinueTimeout: c.transportTimeout,
|
|
|
|
TLSHandshakeTimeout: c.transportTimeout,
|
|
|
|
TLSClientConfig: &tls.Config{
|
|
|
|
InsecureSkipVerify: false,
|
|
|
|
RootCAs: rootCAs,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
doLogRequestBodyOnDebug: c.doLogRequestBodyOnDebug,
|
|
|
|
doLogResponseBodyOnDebug: c.doLogResponseBodyOnDebug,
|
|
|
|
token: c.token,
|
|
|
|
username: c.username,
|
|
|
|
password: c.password,
|
|
|
|
}
|
2021-08-19 11:29:33 +02:00
|
|
|
|
2022-02-23 10:30:19 +02:00
|
|
|
for _, certificate := range c.trustedCerts {
|
2021-08-19 11:29:33 +02:00
|
|
|
filename := path.Base(certificate)
|
|
|
|
filename = strings.ReplaceAll(filename, " ", "")
|
|
|
|
target := filepath.Join(trustStoreDir, filename)
|
2022-03-23 11:02:00 +02:00
|
|
|
if exists, _ := c.getFileUtils().FileExists(target); !exists {
|
2021-08-19 11:29:33 +02:00
|
|
|
log.Entry().WithField("source", certificate).WithField("target", target).Info("Downloading TLS certificate")
|
|
|
|
request, err := http.NewRequest("GET", certificate, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
httpClient := &http.Client{}
|
|
|
|
httpClient.Timeout = c.maxRequestDuration
|
|
|
|
httpClient.Jar = c.cookieJar
|
|
|
|
if !c.useDefaultTransport {
|
|
|
|
httpClient.Transport = transport
|
|
|
|
}
|
|
|
|
response, err := httpClient.Do(request)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "HTTP %v request to %v failed", request.Method, request.URL)
|
|
|
|
}
|
|
|
|
|
|
|
|
if response.StatusCode >= 200 && response.StatusCode < 300 {
|
|
|
|
defer response.Body.Close()
|
|
|
|
parent := filepath.Dir(target)
|
|
|
|
if len(parent) > 0 {
|
2022-03-23 11:02:00 +02:00
|
|
|
if err = c.getFileUtils().MkdirAll(parent, 0777); err != nil {
|
2021-08-19 11:29:33 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2022-03-23 11:02:00 +02:00
|
|
|
fileHandler, err := c.getFileUtils().Create(target)
|
2021-08-19 11:29:33 +02:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "unable to create file %v", filename)
|
|
|
|
}
|
|
|
|
defer fileHandler.Close()
|
|
|
|
|
2022-02-23 10:30:19 +02:00
|
|
|
numWritten, err := io.Copy(fileHandler, response.Body)
|
2021-08-19 11:29:33 +02:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "unable to copy content from url to file %v", filename)
|
|
|
|
}
|
2022-02-23 10:30:19 +02:00
|
|
|
log.Entry().Debugf("wrote %v bytes from response body to file", numWritten)
|
2021-08-19 11:29:33 +02:00
|
|
|
|
|
|
|
certs, err := ioutil.ReadFile(target)
|
|
|
|
if err != nil {
|
2022-02-23 10:30:19 +02:00
|
|
|
return errors.Wrapf(err, "failed to read cert file %v", certificate)
|
2021-08-19 11:29:33 +02:00
|
|
|
}
|
|
|
|
// Append our cert to the system pool
|
2022-02-23 10:30:19 +02:00
|
|
|
ok := rootCAs.AppendCertsFromPEM(certs)
|
|
|
|
if !ok {
|
|
|
|
return errors.Errorf("failed to append %v to root CA store", certificate)
|
2021-08-19 11:29:33 +02:00
|
|
|
}
|
|
|
|
log.Entry().Infof("%v appended to root CA successfully", certificate)
|
|
|
|
} else {
|
|
|
|
return errors.Wrapf(err, "Download of TLS certificate %v failed with status code %v", certificate, response.StatusCode)
|
|
|
|
}
|
|
|
|
} else {
|
2022-11-10 14:17:21 +02:00
|
|
|
log.Entry().Debugf("existing certificate file %v found, appending it to rootCA", target)
|
2021-08-19 11:29:33 +02:00
|
|
|
certs, err := ioutil.ReadFile(target)
|
|
|
|
if err != nil {
|
2022-02-23 10:30:19 +02:00
|
|
|
return errors.Wrapf(err, "failed to read cert file %v", certificate)
|
2021-08-19 11:29:33 +02:00
|
|
|
}
|
|
|
|
// Append our cert to the system pool
|
2022-02-23 10:30:19 +02:00
|
|
|
ok := rootCAs.AppendCertsFromPEM(certs)
|
|
|
|
if !ok {
|
|
|
|
return errors.Errorf("failed to append %v to root CA store", certificate)
|
2021-08-19 11:29:33 +02:00
|
|
|
}
|
2022-11-10 14:17:21 +02:00
|
|
|
log.Entry().Debugf("%v appended to root CA successfully", certificate)
|
2021-08-19 11:29:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-03-28 09:52:15 +02:00
|
|
|
// TrustStoreDirectory default truststore location
|
2022-02-23 10:30:19 +02:00
|
|
|
const TrustStoreDirectory = ".pipeline/trustStore"
|
|
|
|
|
2021-08-19 11:29:33 +02:00
|
|
|
func getWorkingDirForTrustStore() (string, error) {
|
|
|
|
fileUtils := &piperutils.Files{}
|
2022-02-23 10:30:19 +02:00
|
|
|
if exists, _ := fileUtils.DirExists(TrustStoreDirectory); !exists {
|
|
|
|
err := fileUtils.MkdirAll(TrustStoreDirectory, 0777)
|
2021-08-19 11:29:33 +02:00
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "failed to create trust store directory")
|
|
|
|
}
|
|
|
|
}
|
2022-02-23 10:30:19 +02:00
|
|
|
return TrustStoreDirectory, nil
|
2021-08-19 11:29:33 +02:00
|
|
|
}
|
|
|
|
|
2022-03-28 09:52:15 +02:00
|
|
|
// ParseHTTPResponseBodyXML parses an XML http response into a given interface
|
2020-07-14 10:58:57 +02:00
|
|
|
func ParseHTTPResponseBodyXML(resp *http.Response, response interface{}) error {
|
|
|
|
if resp == nil {
|
|
|
|
return errors.Errorf("cannot parse HTTP response with value <nil>")
|
|
|
|
}
|
|
|
|
|
|
|
|
bodyText, readErr := ioutil.ReadAll(resp.Body)
|
|
|
|
if readErr != nil {
|
|
|
|
return errors.Wrap(readErr, "HTTP response body could not be read")
|
|
|
|
}
|
|
|
|
|
|
|
|
marshalErr := xml.Unmarshal(bodyText, &response)
|
|
|
|
if marshalErr != nil {
|
|
|
|
return errors.Wrapf(marshalErr, "HTTP response body could not be parsed as XML: %v", string(bodyText))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-24 07:41:06 +02:00
|
|
|
// ParseHTTPResponseBodyJSON parses a JSON http response into a given interface
|
2020-07-14 10:58:57 +02:00
|
|
|
func ParseHTTPResponseBodyJSON(resp *http.Response, response interface{}) error {
|
|
|
|
if resp == nil {
|
|
|
|
return errors.Errorf("cannot parse HTTP response with value <nil>")
|
|
|
|
}
|
|
|
|
|
|
|
|
bodyText, readErr := ioutil.ReadAll(resp.Body)
|
|
|
|
if readErr != nil {
|
|
|
|
return errors.Wrapf(readErr, "HTTP response body could not be read")
|
|
|
|
}
|
|
|
|
|
|
|
|
marshalErr := json.Unmarshal(bodyText, &response)
|
|
|
|
if marshalErr != nil {
|
|
|
|
return errors.Wrapf(marshalErr, "HTTP response body could not be parsed as JSON: %v", string(bodyText))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|