487 lines
13 KiB
Go
487 lines
13 KiB
Go
// Copyright 2015 Vadim Kravcenko
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
// not use this file except in compliance with the License. You may obtain
|
|
// a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package jenkins
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/form3tech-oss/jwt-go"
|
|
|
|
authtoken "kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
|
|
"kubesphere.io/kubesphere/pkg/simple/client/devops"
|
|
)
|
|
|
|
// Request Methods
|
|
|
|
type APIRequest struct {
|
|
Method string
|
|
Endpoint string
|
|
Payload io.Reader
|
|
Headers http.Header
|
|
Suffix string
|
|
}
|
|
|
|
// set basic token for jenkins auth
|
|
func SetBasicBearTokenHeader(header *http.Header) error {
|
|
bearTokenArray := strings.Split(header.Get("Authorization"), " ")
|
|
bearFlag := bearTokenArray[0]
|
|
var err error
|
|
if strings.ToLower(bearFlag) == "bearer" {
|
|
bearToken := bearTokenArray[1]
|
|
if err != nil {
|
|
return err
|
|
}
|
|
claim := authtoken.Claims{}
|
|
parser := jwt.Parser{}
|
|
_, _, err = parser.ParseUnverified(bearToken, &claim)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
creds := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", claim.Username, bearToken)))
|
|
header.Set("Authorization", fmt.Sprintf("Basic %s", creds))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ar *APIRequest) SetHeader(key string, value string) *APIRequest {
|
|
ar.Headers.Set(key, value)
|
|
return ar
|
|
}
|
|
|
|
func NewAPIRequest(method string, endpoint string, payload io.Reader) *APIRequest {
|
|
var headers = http.Header{}
|
|
var suffix string
|
|
ar := &APIRequest{method, endpoint, payload, headers, suffix}
|
|
return ar
|
|
}
|
|
|
|
type Requester struct {
|
|
Base string
|
|
BasicAuth *BasicAuth
|
|
Client *http.Client
|
|
CACert []byte
|
|
SslVerify bool
|
|
connControl chan struct{}
|
|
}
|
|
|
|
func (r *Requester) SetCrumb(ar *APIRequest) error {
|
|
crumbData := map[string]string{}
|
|
response, err := r.GetJSON("/crumbIssuer/api/json", &crumbData, nil)
|
|
if err != nil {
|
|
jenkinsError, ok := err.(*devops.ErrorResponse)
|
|
if ok && jenkinsError.Response.StatusCode == http.StatusNotFound {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
if response.StatusCode == 200 && crumbData["crumbRequestField"] != "" {
|
|
ar.SetHeader(crumbData["crumbRequestField"], crumbData["crumb"])
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Requester) PostJSON(endpoint string, payload io.Reader, responseStruct interface{}, querystring map[string]string) (*http.Response, error) {
|
|
ar := NewAPIRequest("POST", endpoint, payload)
|
|
if err := r.SetCrumb(ar); err != nil {
|
|
return nil, err
|
|
}
|
|
ar.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
|
ar.Suffix = "api/json"
|
|
return r.Do(ar, responseStruct, querystring)
|
|
}
|
|
|
|
func (r *Requester) Post(endpoint string, payload io.Reader, responseStruct interface{}, querystring map[string]string) (*http.Response, error) {
|
|
ar := NewAPIRequest("POST", endpoint, payload)
|
|
if err := r.SetCrumb(ar); err != nil {
|
|
return nil, err
|
|
}
|
|
ar.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
|
ar.Suffix = ""
|
|
return r.Do(ar, responseStruct, querystring)
|
|
}
|
|
func (r *Requester) PostForm(endpoint string, payload io.Reader, responseStruct interface{}, formString map[string]string) (*http.Response, error) {
|
|
ar := NewAPIRequest("POST", endpoint, payload)
|
|
if err := r.SetCrumb(ar); err != nil {
|
|
return nil, err
|
|
}
|
|
ar.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
|
ar.Suffix = ""
|
|
return r.DoPostForm(ar, responseStruct, formString)
|
|
}
|
|
|
|
func (r *Requester) PostFiles(endpoint string, payload io.Reader, responseStruct interface{}, querystring map[string]string, files []string) (*http.Response, error) {
|
|
ar := NewAPIRequest("POST", endpoint, payload)
|
|
if err := r.SetCrumb(ar); err != nil {
|
|
return nil, err
|
|
}
|
|
return r.Do(ar, responseStruct, querystring, files)
|
|
}
|
|
|
|
func (r *Requester) PostXML(endpoint string, xml string, responseStruct interface{}, querystring map[string]string) (*http.Response, error) {
|
|
payload := bytes.NewBuffer([]byte(xml))
|
|
ar := NewAPIRequest("POST", endpoint, payload)
|
|
if err := r.SetCrumb(ar); err != nil {
|
|
return nil, err
|
|
}
|
|
ar.SetHeader("Content-Type", "application/xml;charset=utf-8")
|
|
ar.Suffix = ""
|
|
return r.Do(ar, responseStruct, querystring)
|
|
}
|
|
|
|
func (r *Requester) GetJSON(endpoint string, responseStruct interface{}, query map[string]string) (*http.Response, error) {
|
|
ar := NewAPIRequest("GET", endpoint, nil)
|
|
ar.SetHeader("Content-Type", "application/json")
|
|
ar.Suffix = "api/json"
|
|
return r.Do(ar, responseStruct, query)
|
|
}
|
|
|
|
func (r *Requester) GetXML(endpoint string, responseStruct interface{}, query map[string]string) (*http.Response, error) {
|
|
ar := NewAPIRequest("GET", endpoint, nil)
|
|
ar.SetHeader("Content-Type", "application/xml")
|
|
ar.Suffix = ""
|
|
return r.Do(ar, responseStruct, query)
|
|
}
|
|
|
|
func (r *Requester) Get(endpoint string, responseStruct interface{}, querystring map[string]string) (*http.Response, error) {
|
|
ar := NewAPIRequest("GET", endpoint, nil)
|
|
ar.Suffix = ""
|
|
return r.Do(ar, responseStruct, querystring)
|
|
}
|
|
|
|
func (r *Requester) GetHtml(endpoint string, responseStruct interface{}, querystring map[string]string) (*http.Response, error) {
|
|
ar := NewAPIRequest("GET", endpoint, nil)
|
|
ar.Suffix = ""
|
|
return r.DoGet(ar, responseStruct, querystring)
|
|
}
|
|
|
|
func (r *Requester) SetClient(client *http.Client) *Requester {
|
|
r.Client = client
|
|
return r
|
|
}
|
|
|
|
//Add auth on redirect if required.
|
|
func (r *Requester) redirectPolicyFunc(req *http.Request, via []*http.Request) error {
|
|
if r.BasicAuth != nil {
|
|
req.SetBasicAuth(r.BasicAuth.Username, r.BasicAuth.Password)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Requester) DoGet(ar *APIRequest, responseStruct interface{}, options ...interface{}) (*http.Response, error) {
|
|
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:
|
|
fileUpload = true
|
|
files = v
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
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.NewRequest(ar.Method, URL.String(), body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
} else {
|
|
req, err = http.NewRequest(ar.Method, URL.String(), ar.Payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if r.BasicAuth != nil {
|
|
req.SetBasicAuth(r.BasicAuth.Username, r.BasicAuth.Password)
|
|
}
|
|
req.Close = true
|
|
req.Header.Add("Accept", "*/*")
|
|
for k := range ar.Headers {
|
|
req.Header.Add(k, ar.Headers.Get(k))
|
|
}
|
|
r.connControl <- struct{}{}
|
|
if response, err := r.Client.Do(req); err != nil {
|
|
<-r.connControl
|
|
return nil, err
|
|
} else {
|
|
<-r.connControl
|
|
errorText := response.Header.Get("X-Error")
|
|
if errorText != "" {
|
|
return nil, errors.New(errorText)
|
|
}
|
|
err := CheckResponse(response)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch responseStruct.(type) {
|
|
case *string:
|
|
return r.ReadRawResponse(response, responseStruct)
|
|
default:
|
|
return r.ReadJSONResponse(response, responseStruct)
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func (r *Requester) Do(ar *APIRequest, responseStruct interface{}, options ...interface{}) (*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:
|
|
fileUpload = true
|
|
files = v
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
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.NewRequest(ar.Method, URL.String(), body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
} else {
|
|
req, err = http.NewRequest(ar.Method, URL.String(), ar.Payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if r.BasicAuth != nil {
|
|
req.SetBasicAuth(r.BasicAuth.Username, r.BasicAuth.Password)
|
|
}
|
|
req.Close = true
|
|
req.Header.Add("Accept", "*/*")
|
|
for k := range ar.Headers {
|
|
req.Header.Add(k, ar.Headers.Get(k))
|
|
}
|
|
r.connControl <- struct{}{}
|
|
if response, err := r.Client.Do(req); err != nil {
|
|
<-r.connControl
|
|
return nil, err
|
|
} else {
|
|
<-r.connControl
|
|
errorText := response.Header.Get("X-Error")
|
|
if errorText != "" {
|
|
return nil, errors.New(errorText)
|
|
}
|
|
err := CheckResponse(response)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch responseStruct.(type) {
|
|
case *string:
|
|
return r.ReadRawResponse(response, responseStruct)
|
|
default:
|
|
return r.ReadJSONResponse(response, responseStruct)
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func (r *Requester) DoPostForm(ar *APIRequest, responseStruct interface{}, form map[string]string) (*http.Response, error) {
|
|
|
|
if !strings.HasSuffix(ar.Endpoint, "/") && ar.Method != "POST" {
|
|
ar.Endpoint += "/"
|
|
}
|
|
URL, err := url.Parse(r.Base + ar.Endpoint + ar.Suffix)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
formValue := make(url.Values)
|
|
for k, v := range form {
|
|
formValue.Set(k, v)
|
|
}
|
|
req, err := http.NewRequest("POST", URL.String(), strings.NewReader(formValue.Encode()))
|
|
if r.BasicAuth != nil {
|
|
req.SetBasicAuth(r.BasicAuth.Username, r.BasicAuth.Password)
|
|
}
|
|
req.Close = true
|
|
req.Header.Add("Accept", "*/*")
|
|
for k := range ar.Headers {
|
|
req.Header.Add(k, ar.Headers.Get(k))
|
|
}
|
|
r.connControl <- struct{}{}
|
|
if response, err := r.Client.Do(req); err != nil {
|
|
<-r.connControl
|
|
return nil, err
|
|
} else {
|
|
<-r.connControl
|
|
errorText := response.Header.Get("X-Error")
|
|
if errorText != "" {
|
|
return nil, errors.New(errorText)
|
|
}
|
|
err := CheckResponse(response)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch responseStruct.(type) {
|
|
case *string:
|
|
return r.ReadRawResponse(response, responseStruct)
|
|
default:
|
|
return r.ReadJSONResponse(response, responseStruct)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func (r *Requester) ReadRawResponse(response *http.Response, responseStruct interface{}) (*http.Response, error) {
|
|
defer response.Body.Close()
|
|
|
|
content, err := ioutil.ReadAll(response.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if str, ok := responseStruct.(*string); ok {
|
|
*str = string(content)
|
|
} else {
|
|
return nil, fmt.Errorf("Could not cast responseStruct to *string")
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (r *Requester) ReadJSONResponse(response *http.Response, responseStruct interface{}) (*http.Response, error) {
|
|
defer response.Body.Close()
|
|
err := json.NewDecoder(response.Body).Decode(responseStruct)
|
|
if err != nil && err.Error() == "EOF" {
|
|
return response, nil
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
func CheckResponse(r *http.Response) error {
|
|
|
|
switch r.StatusCode {
|
|
case http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent, http.StatusFound, http.StatusNotModified:
|
|
return nil
|
|
}
|
|
defer r.Body.Close()
|
|
errorResponse := &devops.ErrorResponse{Response: r}
|
|
data, err := ioutil.ReadAll(r.Body)
|
|
if err == nil && data != nil {
|
|
errorResponse.Body = data
|
|
errorResponse.Message = string(data)
|
|
}
|
|
|
|
return errorResponse
|
|
}
|