248 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			248 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
| package client
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 	"net/http/cookiejar"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/bndr/gojenkins"
 | |
| 	"github.com/pkg/errors"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	errorNotFound = errors.New("404")
 | |
| )
 | |
| 
 | |
| // Jenkins defines Jenkins API.
 | |
| type Jenkins interface {
 | |
| 	GenerateToken(userName, tokenName string) (*UserToken, error)
 | |
| 	Info(ctx context.Context) (*gojenkins.ExecutorResponse, error)
 | |
| 	SafeRestart(ctx context.Context) error
 | |
| 	CreateNode(ctx context.Context, name string, numExecutors int, description string, remoteFS string, label string, options ...interface{}) (*gojenkins.Node, error)
 | |
| 	DeleteNode(ctx context.Context, name string) (bool, error)
 | |
| 	CreateFolder(ctx context.Context, name string, parents ...string) (*gojenkins.Folder, error)
 | |
| 	CreateJobInFolder(ctx context.Context, config string, jobName string, parentIDs ...string) (*gojenkins.Job, error)
 | |
| 	CreateJob(ctx context.Context, config string, options ...interface{}) (*gojenkins.Job, error)
 | |
| 	CreateOrUpdateJob(config, jobName string) (*gojenkins.Job, bool, error)
 | |
| 	RenameJob(ctx context.Context, job string, name string) *gojenkins.Job
 | |
| 	CopyJob(ctx context.Context, copyFrom string, newName string) (*gojenkins.Job, error)
 | |
| 	CreateView(ctx context.Context, name string, viewType string) (*gojenkins.View, error)
 | |
| 	DeleteJob(ctx context.Context, name string) (bool, error)
 | |
| 	BuildJob(ctx context.Context, name string, options map[string]string) (int64, error)
 | |
| 	GetNode(ctx context.Context, name string) (*gojenkins.Node, error)
 | |
| 	GetLabel(ctx context.Context, name string) (*gojenkins.Label, error)
 | |
| 	GetBuild(jobName string, number int64) (*gojenkins.Build, error)
 | |
| 	GetJob(ctx context.Context, id string, parentIDs ...string) (*gojenkins.Job, error)
 | |
| 	GetSubJob(ctx context.Context, parentID string, childID string) (*gojenkins.Job, error)
 | |
| 	GetFolder(ctx context.Context, id string, parents ...string) (*gojenkins.Folder, error)
 | |
| 	GetAllNodes(ctx context.Context) ([]*gojenkins.Node, error)
 | |
| 	GetAllBuildIds(ctx context.Context, job string) ([]gojenkins.JobBuild, error)
 | |
| 	GetAllJobNames(context.Context) ([]gojenkins.InnerJob, error)
 | |
| 	GetAllJobs(context.Context) ([]*gojenkins.Job, error)
 | |
| 	GetQueue(context.Context) (*gojenkins.Queue, error)
 | |
| 	GetQueueUrl() string
 | |
| 	GetQueueItem(ctx context.Context, id int64) (*gojenkins.Task, error)
 | |
| 	GetArtifactData(ctx context.Context, id string) (*gojenkins.FingerPrintResponse, error)
 | |
| 	GetPlugins(depth int) (*gojenkins.Plugins, error)
 | |
| 	UninstallPlugin(ctx context.Context, name string) error
 | |
| 	HasPlugin(ctx context.Context, name string) (*gojenkins.Plugin, error)
 | |
| 	InstallPlugin(ctx context.Context, name string, version string) error
 | |
| 	ValidateFingerPrint(ctx context.Context, id string) (bool, error)
 | |
| 	GetView(ctx context.Context, name string) (*gojenkins.View, error)
 | |
| 	GetAllViews(context.Context) ([]*gojenkins.View, error)
 | |
| 	Poll(ctx context.Context) (int, error)
 | |
| 	ExecuteScript(groovyScript string) (logs string, err error)
 | |
| 	GetNodeSecret(name string) (string, error)
 | |
| }
 | |
| 
 | |
| type jenkins struct {
 | |
| 	gojenkins.Jenkins
 | |
| }
 | |
| 
 | |
| // JenkinsAPIConnectionSettings is struct that handle information about Jenkins API connection.
 | |
| type JenkinsAPIConnectionSettings struct {
 | |
| 	Hostname    string
 | |
| 	Port        int
 | |
| 	UseNodePort bool
 | |
| }
 | |
| 
 | |
| type setBearerToken struct {
 | |
| 	rt    http.RoundTripper
 | |
| 	token string
 | |
| }
 | |
| 
 | |
| func (t *setBearerToken) transport() http.RoundTripper {
 | |
| 	if t.rt != nil {
 | |
| 		return t.rt
 | |
| 	}
 | |
| 	return http.DefaultTransport
 | |
| }
 | |
| 
 | |
| func (t *setBearerToken) RoundTrip(r *http.Request) (*http.Response, error) {
 | |
| 	r.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.token))
 | |
| 	return t.transport().RoundTrip(r)
 | |
| }
 | |
| 
 | |
| // CreateOrUpdateJob creates or updates a job from config.
 | |
| func (jenkins *jenkins) CreateOrUpdateJob(config, jobName string) (job *gojenkins.Job, created bool, err error) {
 | |
| 	// create or update
 | |
| 	job, err = jenkins.GetJob(context.TODO(), jobName)
 | |
| 	if isNotFoundError(err) {
 | |
| 		job, err = jenkins.CreateJob(context.TODO(), config, jobName)
 | |
| 		return job, true, errors.WithStack(err)
 | |
| 	} else if err != nil {
 | |
| 		return job, false, errors.WithStack(err)
 | |
| 	}
 | |
| 
 | |
| 	err = job.UpdateConfig(context.TODO(), config)
 | |
| 	return job, false, errors.WithStack(err)
 | |
| }
 | |
| 
 | |
| // BuildJenkinsAPIUrl returns Jenkins API URL.
 | |
| func (j JenkinsAPIConnectionSettings) BuildJenkinsAPIUrl(serviceName string, serviceNamespace string, servicePort int32, serviceNodePort int32) string {
 | |
| 	if j.Hostname == "" && j.Port == 0 {
 | |
| 		return fmt.Sprintf("http://%s.%s:%d", serviceName, serviceNamespace, servicePort)
 | |
| 	}
 | |
| 
 | |
| 	if j.Hostname != "" && j.UseNodePort {
 | |
| 		return fmt.Sprintf("http://%s:%d", j.Hostname, serviceNodePort)
 | |
| 	}
 | |
| 
 | |
| 	return fmt.Sprintf("http://%s:%d", j.Hostname, j.Port)
 | |
| }
 | |
| 
 | |
| // Validate validates jenkins API connection settings.
 | |
| func (j JenkinsAPIConnectionSettings) Validate() error {
 | |
| 	if j.Port > 0 && j.UseNodePort {
 | |
| 		return errors.New("can't use service port and nodePort both. Please use port or nodePort")
 | |
| 	}
 | |
| 
 | |
| 	if j.Port < 0 {
 | |
| 		return errors.New("service port cannot be lower than 0")
 | |
| 	}
 | |
| 
 | |
| 	if (j.Hostname == "" && j.Port > 0) || (j.Hostname == "" && j.UseNodePort) {
 | |
| 		return errors.New("empty hostname is now allowed. Please provide hostname")
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // NewUserAndPasswordAuthorization creates Jenkins API client with user and password authorization.
 | |
| func NewUserAndPasswordAuthorization(url, userName, passwordOrToken string) (Jenkins, error) {
 | |
| 	return newClient(url, userName, passwordOrToken)
 | |
| }
 | |
| 
 | |
| // NewBearerTokenAuthorization creates Jenkins API client with bearer token authorization.
 | |
| func NewBearerTokenAuthorization(url, token string) (Jenkins, error) {
 | |
| 	return newClient(url, "", token)
 | |
| }
 | |
| 
 | |
| func newClient(url, userName, passwordOrToken string) (Jenkins, error) {
 | |
| 	// if strings.HasSuffix(url, "/") {
 | |
| 	// url = url[:len(url)-1]
 | |
| 	url = strings.TrimSuffix(url, "/")
 | |
| 	// }
 | |
| 
 | |
| 	jenkinsClient := &jenkins{}
 | |
| 	jenkinsClient.Server = url
 | |
| 
 | |
| 	var basicAuth *gojenkins.BasicAuth
 | |
| 	jar, err := cookiejar.New(nil)
 | |
| 	if err != nil {
 | |
| 		return nil, errors.Wrap(err, "couldn't create a cookie jar")
 | |
| 	}
 | |
| 
 | |
| 	httpClient := &http.Client{
 | |
| 		Jar:     jar,
 | |
| 		Timeout: 20 * time.Second,
 | |
| 	}
 | |
| 
 | |
| 	if len(userName) > 0 && len(passwordOrToken) > 0 {
 | |
| 		basicAuth = &gojenkins.BasicAuth{Username: userName, Password: passwordOrToken}
 | |
| 	} else {
 | |
| 		httpClient.Transport = &setBearerToken{token: passwordOrToken, rt: httpClient.Transport}
 | |
| 	}
 | |
| 
 | |
| 	jenkinsClient.Requester = &gojenkins.Requester{
 | |
| 		Base:      url,
 | |
| 		SslVerify: true,
 | |
| 		Client:    httpClient,
 | |
| 		BasicAuth: basicAuth,
 | |
| 	}
 | |
| 	if _, err := jenkinsClient.Init(context.TODO()); err != nil {
 | |
| 		return nil, errors.Wrap(err, "couldn't init Jenkins API client")
 | |
| 	}
 | |
| 
 | |
| 	status, err := jenkinsClient.Poll(context.TODO())
 | |
| 	if err != nil {
 | |
| 		return nil, errors.Wrap(err, "couldn't poll data from Jenkins API")
 | |
| 	}
 | |
| 	if status != http.StatusOK {
 | |
| 		return nil, errors.Errorf("couldn't poll data from Jenkins API, invalid status code returned: %d", status)
 | |
| 	}
 | |
| 
 | |
| 	return jenkinsClient, nil
 | |
| }
 | |
| 
 | |
| func isNotFoundError(err error) bool {
 | |
| 	if err != nil {
 | |
| 		return err.Error() == errorNotFound.Error()
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (jenkins *jenkins) GetNodeSecret(name string) (string, error) {
 | |
| 	url := fmt.Sprintf("%s/scriptText", jenkins.Server)
 | |
| 	script := fmt.Sprintf(`println(jenkins.model.Jenkins.getInstance().getComputer("%s").getJnlpMac())`, name)
 | |
| 	payload := bytes.NewBufferString(fmt.Sprintf("script=%s", script))
 | |
| 
 | |
| 	req, err := http.NewRequest("POST", url, payload)
 | |
| 	if err != nil {
 | |
| 		return "", errors.WithStack(err)
 | |
| 	}
 | |
| 	req.SetBasicAuth(jenkins.Requester.BasicAuth.Username, jenkins.Requester.BasicAuth.Password)
 | |
| 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 | |
| 
 | |
| 	client := &http.Client{}
 | |
| 	resp, err := client.Do(req)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	defer func() {
 | |
| 		if cerr := resp.Body.Close(); cerr != nil && err == nil {
 | |
| 			err = cerr
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	body, err := io.ReadAll(resp.Body)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	if resp.StatusCode != http.StatusOK {
 | |
| 		return "", fmt.Errorf("failed to get node secret: %s", string(body))
 | |
| 	}
 | |
| 
 | |
| 	return string(body), nil
 | |
| }
 | |
| 
 | |
| // Returns the list of all plugins installed on the Jenkins server.
 | |
| // You can supply depth parameter, to limit how much data is returned.
 | |
| func (jenkins *jenkins) GetPlugins(depth int) (*gojenkins.Plugins, error) {
 | |
| 	p := gojenkins.Plugins{Jenkins: &jenkins.Jenkins, Raw: new(gojenkins.PluginResponse), Base: "/pluginManager", Depth: depth}
 | |
| 	statusCode, err := p.Poll(context.TODO())
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if statusCode != http.StatusOK {
 | |
| 		return nil, fmt.Errorf("invalid status code returned: %d", statusCode)
 | |
| 	}
 | |
| 	return &p, nil
 | |
| }
 |