func (p *Plugin) getBuildTestResultsURL(userID, channelID, jobName, buildID string) error {
	build, buildErr := p.getBuild(jobName, userID, buildID)
	if buildErr != nil {
		return buildErr
	}
	hasTestResults, hasTestErr := build.HasTestResults()
	if hasTestErr != nil {
		return errors.Wrap(hasTestErr, "Error checking for test results")
	}
	msg := ""
	if hasTestResults {
		job, jobErr := p.getJob(userID, jobName)
		if jobErr != nil {
			return jobErr
		}
		testReportsURL := fmt.Sprintf("%s%d/testReport", job.Raw.URL, build.GetBuildNumber())
		msg = fmt.Sprintf("Test reports for the build #%d of the job '%s': %s", build.GetBuildNumber(), jobName, testReportsURL)
	} else {
		msg = fmt.Sprintf("Build #%d of the job '%s' doesn't have test reports.", build.GetBuildNumber(), jobName)
	}
	p.createPost(userID, channelID, msg)
	return nil
}
getBuildTestResultsURL checks if the specified job and build has test results and
creates a post with the test results URL if the  build has test results.
If build number is not specified, the method checks the last build of the job for test results.
package main
import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"
	"github.com/gorilla/mux"
	"github.com/mattermost/mattermost-server/v5/model"
	"github.com/mattermost/mattermost-server/v5/plugin"
	"github.com/pkg/errors"
	"github.com/waseem18/gojenkins"
)
const (
	jenkinsTokenKey = "_jenkinsToken"
	botUserName     = "jenkins"
	botDisplayName  = "Jenkins"
	botDescription  = "Created by the Jenkins Plugin."
)
type Plugin struct {
	plugin.MattermostPlugin
	router *mux.Router
	configurationLock sync.RWMutex
	configuration *configuration
	botUserID string
}
type JenkinsUserInfo struct {
	UserID   string
	Username string
	Token    string
}
func (p *Plugin) OnActivate() error {
	botUserID, err := p.Helpers.EnsureBot(&model.Bot{
		Username:    botUserName,
		DisplayName: botDisplayName,
		Description: botDescription,
	})
	if err != nil {
		return errors.Wrap(err, "failed to ensure bot")
	}
	p.botUserID = botUserID
	bundlePath, err := p.API.GetBundlePath()
	if err != nil {
		return errors.Wrap(err, "failed to get bundle path")
	}
	profileImage, err := ioutil.ReadFile(filepath.Join(bundlePath, "assets", "jenkins.png"))
	if err != nil {
		return errors.Wrap(err, "failed to read profile image")
	}
	if appErr := p.API.SetProfileImage(botUserID, profileImage); appErr != nil {
		return errors.Wrap(appErr, "failed to set profile image")
	}
	command, err := p.getCommand()
	if err != nil {
		return errors.Wrap(err, "failed to get command")
	}
	if err := p.API.RegisterCommand(command); err != nil {
		p.API.LogWarn("Error registering Jenkins custom command", "err", err)
	}
	p.router = p.InitAPI()
	conf := p.getConfiguration()
	if err := p.IsValid(conf); err != nil {
		return err
	}
	return nil
}
func (p *Plugin) IsValid(configuration *configuration) error {
	if configuration.JenkinsURL == "" {
		return fmt.Errorf("please add Jenkins URL in plugin settings")
	}
	u, err := url.Parse(configuration.JenkinsURL)
	if err != nil {
		return err
	}
	if u.Scheme == "" {
		return fmt.Errorf("please add scheme to the URL. HTTP or HTTPS")
	}
	return nil
}
func (p *Plugin) storeJenkinsUserInfo(info *JenkinsUserInfo) error {
	config := p.getConfiguration()
	encryptedToken, err := encrypt([]byte(config.EncryptionKey), info.Token)
	if err != nil {
		return err
	}
	info.Token = encryptedToken
	jsonInfo, err := json.Marshal(info)
	if err != nil {
		return err
	}
	if err := p.API.KVSet(info.UserID+jenkinsTokenKey, jsonInfo); err != nil {
		return err
	}
	return nil
}
func (p *Plugin) getJenkinsUserInfo(userID string) (*JenkinsUserInfo, error) {
	config := p.getConfiguration()
	var userInfo JenkinsUserInfo
	infoBytes, infoErr := p.API.KVGet(userID + jenkinsTokenKey)
	if infoErr != nil {
		return nil, infoErr
	} else if infoBytes == nil {
		return nil, errors.New("user not found")
	} else if err := json.Unmarshal(infoBytes, &userInfo); err != nil {
		return nil, err
	}
	unencryptedToken, err := decrypt([]byte(config.EncryptionKey), userInfo.Token)
	if err != nil {
		return nil, err
	}
	userInfo.Token = unencryptedToken
	return &userInfo, nil
}
func (p *Plugin) verifyJenkinsCredentials(username, token string) (bool, error) {
	pluginConfig := p.getConfiguration()
	u, err := url.Parse(pluginConfig.JenkinsURL)
	if err != nil {
		return false, err
	}
	scheme := u.Scheme
	response, respErr := http.Get(fmt.Sprintf("%s://%s:%s@%s", scheme, username, token, u.Host))
	if respErr != nil {
		return false, respErr
	}
	defer response.Body.Close()
	if response.StatusCode == 200 {
		return true, nil
	}
	return false, errors.New("error verifying Jenkins credentials")
}
func (p *Plugin) createEphemeralPost(userID, channelID, message string) {
	post := &model.Post{
		UserId:    p.botUserID,
		ChannelId: channelID,
		Message:   message,
		Type:      model.POST_DEFAULT,
	}
	p.API.SendEphemeralPost(userID, post)
}
func (p *Plugin) createPost(userID, channelID, message string, fileIds ...string) {
	userInfo, userInfoErr := p.getJenkinsUserInfo(userID)
	if userInfoErr != nil {
		p.API.LogError("Error fetching Jenkins user details", "err", userInfoErr.Error())
		return
	}
	slackAttachment := generateSlackAttachment(message)
	slackAttachment.Pretext = fmt.Sprintf("Initiated by Jenkins user: %s", userInfo.Username)
	post := &model.Post{
		UserId:    p.botUserID,
		ChannelId: channelID,
		Type:      model.POST_DEFAULT,
		Props: map[string]interface{}{
			"attachments": []*model.SlackAttachment{slackAttachment},
		},
	}
	if len(fileIds) > 0 {
		post.FileIds = append(post.FileIds, fileIds[0])
	}
	if _, err := p.API.CreatePost(post); err != nil {
		p.API.LogError("Could not create a post", "user_id", userID, "err", err.Error())
	}
}
func (p *Plugin) getJenkinsClient(userID string) (*gojenkins.Jenkins, error) {
	pluginConfig := p.getConfiguration()
	userInfo, err := p.getJenkinsUserInfo(userID)
	if err != nil {
		return nil, errors.Wrap(err, "Error fetching Jenkins user information")
	}
	jenkins := gojenkins.CreateJenkins(nil, pluginConfig.JenkinsURL, userInfo.Username, userInfo.Token)
	_, errJenkins := jenkins.Init()
	if errJenkins != nil {
		wrap := errors.Wrap(errJenkins, "Error creating Jenkins client")
		return nil, wrap
	}
	return jenkins, nil
}
func (p *Plugin) getJob(userID, jobName string) (*gojenkins.Job, error) {
	jenkins, jenkinsErr := p.getJenkinsClient(userID)
	if jenkinsErr != nil {
		return nil, errors.Wrap(jenkinsErr, "Error creating Jenkins client")
	}
	containsSlash := strings.Contains(jobName, "/")
	if containsSlash {
		jobName = strings.ReplaceAll(jobName, "/", "/job/")
	}
	job, jobErr := jenkins.GetJob(jobName)
	if jobErr != nil {
		return nil, errors.Wrap(jobErr, "Error fetching job")
	}
	return job, nil
}
func (p *Plugin) getBuild(jobName, userID, buildID string) (*gojenkins.Build, error) {
	job, jobErr := p.getJob(userID, jobName)
	if jobErr != nil {
		return nil, jobErr
	}
	var build *gojenkins.Build
	var buildErr error
	if buildID == "" {
		build, buildErr = job.GetLastBuild()
		if buildErr != nil {
			return nil, errors.Wrap(buildErr, "Error fetching last build")
		}
		return build, nil
	}
	buildIDInt, _ := strconv.ParseInt(buildID, 10, 64)
	build, buildErr = job.GetBuild(buildIDInt)
	if buildErr != nil {
		return nil, errors.Wrap(buildErr, "Error fetching the build")
	}
	return build, nil
}
func (p *Plugin) triggerJenkinsJob(userID, channelID, jobName string, parameters map[string]string) (*gojenkins.Build, error) {
	jenkins, jenkinsErr := p.getJenkinsClient(userID)
	if jenkinsErr != nil {
		return nil, errors.Wrap(jenkinsErr, "Error creating Jenkins client")
	}
	containsSlash := strings.Contains(jobName, "/")
	if containsSlash {
		jobName = strings.ReplaceAll(jobName, "/", "/job/")
	}
	buildQueueID, buildErr := p.buildJenkinsJob(jenkins, userID, channelID, jobName, parameters)
	if buildErr != nil {
		return nil, buildErr
	}
	build, err := p.checkIfJobHasStarted(jenkins, jobName, buildQueueID)
	if err != nil {
		return nil, err
	}
	return build, nil
}
func (p *Plugin) buildJenkinsJob(jenkins *gojenkins.Jenkins, userID, channelID, jobName string, parameters map[string]string) (int64, error) {
	buildQueueID, buildErr := jenkins.BuildJob(jobName, parameters)
	if buildErr != nil {
		return -1, errors.Wrap(buildErr, "Error building job")
	}
	if buildQueueID == 0 {
		p.createEphemeralPost(userID, channelID, "A build of this job is still in queue.\n Please trigger the job after the job's build queue is free.")
		return -1, errors.Wrap(buildErr, "Error building the job as a previous build is still in queue.")
	}
	p.createPost(userID, channelID, fmt.Sprintf("Job '%s' has been triggered and is in queue.", strings.ReplaceAll(jobName, "/job/", "/")))
	return buildQueueID, nil
}
func (p *Plugin) checkIfJobHasStarted(jenkins *gojenkins.Jenkins, jobName string, buildQueueID int64) (*gojenkins.Build, error) {
	task, taskErr := jenkins.GetQueueItem(buildQueueID)
	if taskErr != nil {
		return nil, taskErr
	}
	for {
		if task.Raw.Executable.URL != "" {
			break
		}
		time.Sleep(pollingSleepTime * time.Second)
		if _, err := task.Poll(); err != nil {
			p.API.LogWarn("Error polling jenkins job to check the build status", "err", err)
		}
	}
	buildInfo, buildErr := jenkins.GetBuild(jobName, task.Raw.Executable.Number)
	if buildErr != nil {
		return nil, errors.Wrap(buildErr, "Error gettting job details")
	}
	return buildInfo, nil
}
func (p *Plugin) fetchAndUploadArtifactsOfABuild(userID, channelID, jobName, buildID string) error {
	config := p.API.GetConfig()
	build, buildErr := p.getBuild(jobName, userID, buildID)
	if buildErr != nil {
		return buildErr
	}
	artifacts := build.GetArtifacts()
	if len(artifacts) == 0 {
		p.createPost(userID, channelID, fmt.Sprintf("No artifacts found in the build #%d of the job '%s'", build.GetBuildNumber(), jobName))
	} else {
		p.createPost(userID, channelID, fmt.Sprintf("%d Artifact(s) found in the build #%d of the job '%s'", len(artifacts), build.GetBuildNumber(), jobName))
	}
	for _, a := range artifacts {
		fileData, fileDataErr := a.GetData()
		if fileDataErr != nil {
			return errors.Wrap(fileDataErr, "Error fetching file data")
		}
		p.createEphemeralPost(userID, channelID, fmt.Sprintf("Uploading artifact '%s' ...", a.FileName))
		fileInfo, fileInfoErr := p.API.UploadFile(fileData, channelID, a.FileName)
		if fileInfoErr != nil {
			return errors.Wrap(fileInfoErr, "Error uploading file")
		}
		p.createPost(userID, channelID, fmt.Sprintf("Artifact '%s' : %s", fileInfo.Name, *config.ServiceSettings.SiteURL+"/api/v4/files/"+fileInfo.Id))
	}
	return nil
}