From 7477efab05092fb5906aef5f237203480f276454 Mon Sep 17 00:00:00 2001 From: Kiryl Shmarlouski Date: Thu, 30 Mar 2023 14:43:14 +0300 Subject: [PATCH 1/4] Job parameters are passed to Jenkins correctly with Job.Invoke() method --- job.go | 11 ++++++----- request.go | 8 ++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/job.go b/job.go index 1936af79..5ae4294a 100644 --- a/job.go +++ b/job.go @@ -17,7 +17,6 @@ package gojenkins import ( "bytes" "context" - "encoding/json" "errors" "fmt" "net/url" @@ -499,14 +498,16 @@ func (j *Job) Invoke(ctx context.Context, files []string, skipIfRunning bool, pa base = "/build" } reqParams := map[string]string{} - buildParams := map[string]string{} if securityToken != "" { reqParams["token"] = securityToken } - buildParams["json"] = string(makeJson(params)) - b, _ := json.Marshal(buildParams) - resp, err := j.Jenkins.Requester.PostFiles(ctx, j.Base+base, bytes.NewBuffer(b), nil, reqParams, files) + data := url.Values{} + for k, v := range params { + data.Set(k, v) + } + + resp, err := j.Jenkins.Requester.PostFiles(ctx, j.Base+base, bytes.NewBufferString(data.Encode()), nil, reqParams, files) if err != nil { return false, err } diff --git a/request.go b/request.go index c67aab16..f46d8daa 100644 --- a/request.go +++ b/request.go @@ -99,6 +99,8 @@ func (r *Requester) PostFiles(ctx context.Context, endpoint string, payload io.R if err := r.SetCrumb(ctx, ar); err != nil { return nil, err } + ar.SetHeader("Content-Type", "application/x-www-form-urlencoded") + ar.Suffix = "" return r.Do(ctx, ar, &responseStruct, querystring, files) } @@ -170,8 +172,10 @@ func (r *Requester) Do(ctx context.Context, ar *APIRequest, responseStruct inter URL.RawQuery = querystring.Encode() case []string: - fileUpload = true - files = v + if v != nil { + fileUpload = true + files = v + } } } var req *http.Request From 3bbdd8bb1b1b8efa7c686ae40d5ac6f0bffc5094 Mon Sep 17 00:00:00 2001 From: Kiryl Shmarlouski Date: Thu, 30 Mar 2023 15:59:12 +0300 Subject: [PATCH 2/4] File parameter is passed correctly --- job.go | 13 +++---------- request.go | 2 -- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/job.go b/job.go index 5ae4294a..308f2852 100644 --- a/job.go +++ b/job.go @@ -17,6 +17,7 @@ package gojenkins import ( "bytes" "context" + "encoding/json" "errors" "fmt" "net/url" @@ -493,21 +494,13 @@ func (j *Job) Invoke(ctx context.Context, files []string, skipIfRunning bool, pa params = make(map[string]string) } - // If files are specified - url is /build - if files != nil { - base = "/build" - } reqParams := map[string]string{} if securityToken != "" { reqParams["token"] = securityToken } - data := url.Values{} - for k, v := range params { - data.Set(k, v) - } - - resp, err := j.Jenkins.Requester.PostFiles(ctx, j.Base+base, bytes.NewBufferString(data.Encode()), nil, reqParams, files) + paramBytes, _ := json.Marshal(params) + resp, err := j.Jenkins.Requester.PostFiles(ctx, j.Base+base, bytes.NewBuffer(paramBytes), nil, reqParams, files) if err != nil { return false, err } diff --git a/request.go b/request.go index f46d8daa..48585843 100644 --- a/request.go +++ b/request.go @@ -99,8 +99,6 @@ func (r *Requester) PostFiles(ctx context.Context, endpoint string, payload io.R if err := r.SetCrumb(ctx, ar); err != nil { return nil, err } - ar.SetHeader("Content-Type", "application/x-www-form-urlencoded") - ar.Suffix = "" return r.Do(ctx, ar, &responseStruct, querystring, files) } From dfc78baac01b35820efe3d60ee9693f9cf93eb94 Mon Sep 17 00:00:00 2001 From: Kiryl Shmarlouski Date: Fri, 31 Mar 2023 10:04:51 +0300 Subject: [PATCH 3/4] File parameters for request moved from r.Do() to r.PostFiles() --- job.go | 4 +- request.go | 112 +++++++++++++++++++++++------------------------------ 2 files changed, 50 insertions(+), 66 deletions(-) diff --git a/job.go b/job.go index 308f2852..6e214651 100644 --- a/job.go +++ b/job.go @@ -468,7 +468,7 @@ func (j *Job) InvokeSimple(ctx context.Context, params map[string]string) (int64 return number, nil } -func (j *Job) Invoke(ctx context.Context, files []string, skipIfRunning bool, params map[string]string, cause string, securityToken string) (bool, error) { +func (j *Job) Invoke(ctx context.Context, files map[string]string, skipIfRunning bool, params map[string]string, cause string, securityToken string) (bool, error) { isQueued, err := j.IsQueued(ctx) if err != nil { return false, err @@ -488,7 +488,7 @@ func (j *Job) Invoke(ctx context.Context, files []string, skipIfRunning bool, pa base := "/build" // If parameters are specified - url is /builWithParameters - if params != nil { + if params != nil || files != nil { base = "/buildWithParameters" } else { params = make(map[string]string) diff --git a/request.go b/request.go index 48585843..a7057d4d 100644 --- a/request.go +++ b/request.go @@ -94,12 +94,49 @@ func (r *Requester) Post(ctx context.Context, endpoint string, payload io.Reader return r.Do(ctx, ar, &responseStruct, querystring) } -func (r *Requester) PostFiles(ctx context.Context, endpoint string, payload io.Reader, responseStruct interface{}, querystring map[string]string, files []string) (*http.Response, error) { +func (r *Requester) PostFiles(ctx context.Context, endpoint string, payload io.Reader, responseStruct interface{}, querystring map[string]string, files map[string]string) (*http.Response, error) { ar := NewAPIRequest("POST", endpoint, payload) if err := r.SetCrumb(ctx, ar); err != nil { return nil, err } - return r.Do(ctx, ar, &responseStruct, querystring, files) + + var err error + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + if files != nil || len(files) != 0 { + for fieldname, fileName := range files { + fileData, err := os.Open(fileName) + if err != nil { + Error.Println(err.Error()) + return nil, err + } + + part, err := writer.CreateFormFile(fieldname, filepath.Base(fileName)) + if err != nil { + Error.Println(err.Error()) + return nil, err + } + if _, err = io.Copy(part, fileData); err != nil { + return nil, err + } + defer fileData.Close() + } + } + + var params map[string]string + json.NewDecoder(ar.Payload).Decode(¶ms) + for key, val := range params { + if err = writer.WriteField(key, val); err != nil { + return nil, err + } + } + if err = writer.Close(); err != nil { + return nil, err + } + ar.Payload = body + ar.Headers.Add("Content-Type", writer.FormDataContentType()) + + return r.Do(ctx, ar, &responseStruct, querystring) } func (r *Requester) PostXML(ctx context.Context, endpoint string, xml string, responseStruct interface{}, querystring map[string]string) (*http.Response, error) { @@ -146,79 +183,26 @@ func (r *Requester) redirectPolicyFunc(req *http.Request, via []*http.Request) e return nil } -func (r *Requester) Do(ctx context.Context, ar *APIRequest, responseStruct interface{}, options ...interface{}) (*http.Response, error) { +func (r *Requester) Do(ctx context.Context, ar *APIRequest, responseStruct interface{}, options map[string]string) (*http.Response, error) { if !strings.HasSuffix(ar.Endpoint, "/") && ar.Method != "POST" { ar.Endpoint += "/" } - fileUpload := false - var files []string URL, err := url.Parse(r.Base + ar.Endpoint + ar.Suffix) - if err != nil { return nil, err } - for _, o := range options { - switch v := o.(type) { - case map[string]string: - - querystring := make(url.Values) - for key, val := range v { - querystring.Set(key, val) - } - - URL.RawQuery = querystring.Encode() - case []string: - if v != nil { - fileUpload = true - files = v - } - } + querystring := make(url.Values) + for key, val := range options { + querystring.Set(key, val) } - var req *http.Request - - if fileUpload { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - for _, file := range files { - fileData, err := os.Open(file) - if err != nil { - Error.Println(err.Error()) - return nil, err - } + URL.RawQuery = querystring.Encode() - part, err := writer.CreateFormFile("file", filepath.Base(file)) - if err != nil { - Error.Println(err.Error()) - return nil, err - } - if _, err = io.Copy(part, fileData); err != nil { - return nil, err - } - defer fileData.Close() - } - var params map[string]string - json.NewDecoder(ar.Payload).Decode(¶ms) - for key, val := range params { - if err = writer.WriteField(key, val); err != nil { - return nil, err - } - } - if err = writer.Close(); err != nil { - return nil, err - } - req, err = http.NewRequestWithContext(ctx, ar.Method, URL.String(), body) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", writer.FormDataContentType()) - } else { - - req, err = http.NewRequestWithContext(ctx, ar.Method, URL.String(), ar.Payload) - if err != nil { - return nil, err - } + var req *http.Request + req, err = http.NewRequestWithContext(ctx, ar.Method, URL.String(), ar.Payload) + if err != nil { + return nil, err } if r.BasicAuth != nil { From 5d8f7ba8b9cc15311b5f095007296ab8ba73ce11 Mon Sep 17 00:00:00 2001 From: Kiryl Shmarlouski Date: Fri, 31 Mar 2023 10:20:26 +0300 Subject: [PATCH 4/4] job.Invoke() returns ID, same as job.InvokeSimple() --- job.go | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/job.go b/job.go index 6e214651..666e7534 100644 --- a/job.go +++ b/job.go @@ -468,21 +468,21 @@ func (j *Job) InvokeSimple(ctx context.Context, params map[string]string) (int64 return number, nil } -func (j *Job) Invoke(ctx context.Context, files map[string]string, skipIfRunning bool, params map[string]string, cause string, securityToken string) (bool, error) { +func (j *Job) Invoke(ctx context.Context, files map[string]string, skipIfRunning bool, params map[string]string, cause string, securityToken string) (int64, error) { isQueued, err := j.IsQueued(ctx) if err != nil { - return false, err + return 0, err } if isQueued { Error.Printf("%s is already running", j.GetName()) - return false, nil + return 0, nil } isRunning, err := j.IsRunning(ctx) if err != nil { - return false, err + return 0, err } if isRunning && skipIfRunning { - return false, fmt.Errorf("Will not request new build because %s is already running", j.GetName()) + return 0, fmt.Errorf("Will not request new build because %s is already running", j.GetName()) } base := "/build" @@ -502,12 +502,28 @@ func (j *Job) Invoke(ctx context.Context, files map[string]string, skipIfRunning paramBytes, _ := json.Marshal(params) resp, err := j.Jenkins.Requester.PostFiles(ctx, j.Base+base, bytes.NewBuffer(paramBytes), nil, reqParams, files) if err != nil { - return false, err + return 0, err + } + if resp.StatusCode != 200 && resp.StatusCode != 201 { + return 0, fmt.Errorf("Could not invoke job %q: %s", j.GetName(), resp.Status) + } + + location := resp.Header.Get("Location") + if location == "" { + return 0, errors.New("Don't have key \"Location\" in response of header") } - if resp.StatusCode == 200 || resp.StatusCode == 201 { - return true, nil + + u, err := url.Parse(location) + if err != nil { + return 0, err } - return false, errors.New(strconv.Itoa(resp.StatusCode)) + + number, err := strconv.ParseInt(path.Base(u.Path), 10, 64) + if err != nil { + return 0, err + } + + return number, nil } func (j *Job) Poll(ctx context.Context) (int, error) {