未验证 提交 9ec50e80 编写于 作者: LinuxSuRen's avatar LinuxSuRen 提交者: GitHub

Add support to input pipeline job (#164)

* Add support to input pipeline job

* Try to fix the 400 error

* Add support get input actions

* Add test cases

* Remove the new badges

put them instead of another pr

* Add the action as a argument
上级 554ec5bb
package cmd
import (
"fmt"
"log"
"net/http"
"strconv"
"encoding/json"
"github.com/AlecAivazis/survey/v2"
"github.com/jenkins-zh/jenkins-cli/client"
"github.com/spf13/cobra"
"github.com/AlecAivazis/survey/v2/terminal"
)
// JobInputOption is the job delete option
type JobInputOption struct {
BatchOption
Action string
RoundTripper http.RoundTripper
Stdio terminal.Stdio
}
var jobInputOption JobInputOption
func init() {
jobCmd.AddCommand(jobInputCmd)
jobInputCmd.Flags().StringVarP(&jobInputOption.Action, "action", "", "", "The action wether you want to process or abort.")
}
var jobInputCmd = &cobra.Command{
Use: "input <jobName> [buildID]",
Short: "Input a job in your Jenkins",
Long: `Input a job in your Jenkins`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
cmd.Help()
return
}
jobName := args[0]
buildID := -1
if len(args) >= 2 {
var err error
if buildID, err = strconv.Atoi(args[1]); err != nil {
cmd.PrintErrln(err)
}
}
jclient := &client.JobClient{
JenkinsCore: client.JenkinsCore{
RoundTripper: jobInputOption.RoundTripper,
Debug: rootOptions.Debug,
},
}
getCurrentJenkinsAndClient(&(jclient.JenkinsCore))
if inputActions, err := jclient.GetJobInputActions(jobName, buildID); err != nil {
log.Fatal(err)
} else if len(inputActions) >= 1 {
inputAction := inputActions[0]
params := make(map[string]string)
if len(inputAction.Inputs) > 0 {
inputsJSON, _ := json.MarshalIndent(inputAction.Inputs, "", " ")
content := string(inputsJSON)
prompt := &survey.Editor{
Message: "Edit your pipeline input parameters",
FileName: "*.json",
Default: content,
HideDefault: true,
AppendDefault: true,
}
if err = survey.AskOne(prompt, &content); err != nil {
log.Fatal(err)
}
if err = json.Unmarshal([]byte(content), &(inputAction.Inputs)); err != nil {
log.Fatal(err)
}
for _, input := range inputAction.Inputs {
params[input.Name] = input.Value
}
}
render := &survey.Renderer{}
render.WithStdio(jobInputOption.Stdio)
// allow users make their choice through cli arguments
action := jobInputOption.Action
if action == "" {
prompt := &survey.Input{
Renderer: *render,
Message: fmt.Sprintf("Are you going to process or abort this input: %s?", inputAction.Message),
}
survey.AskOne(prompt, &action)
}
if action == "process" {
err = jclient.JobInputSubmit(jobName, inputAction.ID, buildID, false, params)
} else if action == "abort" {
err = jclient.JobInputSubmit(jobName, inputAction.ID, buildID, true, params)
} else {
cmd.PrintErrln("Only process or abort is accepted!")
}
if err != nil {
cmd.PrintErrln(err)
}
}
},
}
package cmd
import (
"bytes"
"io/ioutil"
"os"
"fmt"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/spf13/cobra"
"github.com/jenkins-zh/jenkins-cli/mock/mhttp"
"github.com/jenkins-zh/jenkins-cli/client"
// "github.com/AlecAivazis/survey/v2/core"
// "github.com/AlecAivazis/survey/v2/terminal"
)
var _ = Describe("job input command", func() {
var (
ctrl *gomock.Controller
roundTripper *mhttp.MockRoundTripper
jenkinsRoot string
username string
token string
)
BeforeEach(func() {
ctrl = gomock.NewController(GinkgoT())
roundTripper = mhttp.NewMockRoundTripper(ctrl)
jobInputOption.RoundTripper = roundTripper
rootCmd.SetArgs([]string{})
rootOptions.Jenkins = ""
rootOptions.ConfigFile = "test.yaml"
jenkinsRoot = "http://localhost:8080/jenkins"
username = "admin"
token = "111e3a2f0231198855dceaff96f20540a9"
})
AfterEach(func() {
rootCmd.SetArgs([]string{})
os.Remove(rootOptions.ConfigFile)
rootOptions.ConfigFile = ""
ctrl.Finish()
})
Context("basic cases", func() {
It("no params, will error",func(){
data, err := generateSampleConfig()
Expect(err).To(BeNil())
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
rootCmd.SetArgs([]string{"job", "input"})
jobInputCmd.SetHelpFunc(func(cmd *cobra.Command, _ []string) {
cmd.Print("help")
})
buf := new(bytes.Buffer)
rootCmd.SetOutput(buf)
_, err = rootCmd.ExecuteC()
Expect(err).To(BeNil())
Expect(buf.String()).To(Equal("help"))
})
It("should success, abort without inputs", func() {
data, err := generateSampleConfig()
Expect(err).To(BeNil())
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
jobName := "test"
buildID := 1
client.PrepareForGetJobInputActions(roundTripper, jenkinsRoot, username, token, jobName, buildID)
client.PrepareForSubmitInput(roundTripper, jenkinsRoot, fmt.Sprintf("/job/%s", jobName) , username, token)
// no idea how to let it works, just leave this here
// _, w, err := os.Pipe()
// c, err := expect.NewConsole(expect.WithStdout(w))
// Expect(err).To(BeNil())
// jobInputOption.Stdio = terminal.Stdio{
// In:c.Tty(),
// Out:c.Tty(),
// Err:c.Tty(),
// }
// defer c.Close()
// go func() {
// c.ExpectString("Are you going to process or abort this input: message?")
// c.SendLine("abort\n")
// c.ExpectEOF()
// }()
rootCmd.SetArgs([]string{"job", "input", jobName, "1", "--action", "abort"})
buf := new(bytes.Buffer)
rootCmd.SetOutput(buf)
_, err = rootCmd.ExecuteC()
Expect(err).To(BeNil())
Expect(buf.String()).To(Equal(""))
})
It("should success, process without inputs", func() {
data, err := generateSampleConfig()
Expect(err).To(BeNil())
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
jobName := "test"
buildID := 1
client.PrepareForGetJobInputActions(roundTripper, jenkinsRoot, username, token, jobName, buildID)
client.PrepareForSubmitProcessInput(roundTripper, jenkinsRoot, fmt.Sprintf("/job/%s", jobName) , username, token)
rootCmd.SetArgs([]string{"job", "input", jobName, "1", "--action", "process"})
buf := new(bytes.Buffer)
rootCmd.SetOutput(buf)
_, err = rootCmd.ExecuteC()
Expect(err).To(BeNil())
Expect(buf.String()).To(Equal(""))
})
})
})
......@@ -297,6 +297,47 @@ func (q *JobClient) Delete(jobName string) (err error) {
return
}
// GetJobInputActions returns the all pending actions
func (q *JobClient) GetJobInputActions(jobName string, buildID int) (actions []JobInputItem, err error) {
path := parseJobPath(jobName)
err = q.RequestWithData("GET", fmt.Sprintf("%s/%d/wfapi/pendingInputActions", path, buildID), nil, nil, 200, &actions)
return
}
// JenkinsInputParametersRequest represents the parameters for the Jenkins input request
type JenkinsInputParametersRequest struct {
Parameter []ParameterDefinition `json:"parameter"`
}
// JobInputSubmit submit the pending input request
func (q *JobClient) JobInputSubmit(jobName, inputID string, buildID int, abort bool, params map[string]string) (err error) {
jobPath := parseJobPath(jobName)
var api string
if abort {
api = fmt.Sprintf("%s/%d/input/%s/abort", jobPath, buildID, inputID)
} else {
api = fmt.Sprintf("%s/%d/input/%s/proceed", jobPath, buildID, inputID)
}
request := JenkinsInputParametersRequest{
Parameter: make([]ParameterDefinition, 0),
}
for k, v := range params {
request.Parameter = append(request.Parameter, ParameterDefinition{
Name: k,
Value: v,
})
}
paramData, _ := json.Marshal(request)
api = fmt.Sprintf("%s?json=%s", api, string(paramData))
_, err = q.RequestWithoutData("POST", api, nil, nil, 200)
return
}
// parseJobPath leads with slash
func parseJobPath(jobName string) (path string) {
jobItems := strings.Split(jobName, " ")
......@@ -405,3 +446,14 @@ type JobCategoryItem struct {
Order int
Class string
}
// JobInputItem represents a job input action
type JobInputItem struct {
ID string
AbortURL string
Message string
ProceedText string
ProceedURL string
RedirectApprovalURL string
Inputs []ParameterDefinition
}
......@@ -305,4 +305,22 @@ var _ = Describe("job test", func() {
Expect(err).To(BeNil())
})
})
Context("GetJobInputActions", func() {
It("simple case, should success", func() {
PrepareForGetJobInputActions(roundTripper, jobClient.URL, "", "", "jobName", 1)
actions, err := jobClient.GetJobInputActions("jobName", 1)
Expect(err).To(BeNil())
Expect(len(actions)).To(Equal(1))
Expect(actions[0].Message).To(Equal("message"))
})
})
Context("JobInputSubmit", func() {
It("simple case, should success", func() {
PrepareForSubmitInput(roundTripper, jobClient.URL, "/job/jobName", "", "")
err := jobClient.JobInputSubmit("jobName", "Eff7d5dba32b4da32d9a67a519434d3f", 1, true, nil)
Expect(err).To(BeNil())
})
})
})
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"github.com/jenkins-zh/jenkins-cli/mock/mhttp"
)
// PrepareForGetJobInputActions only for test
func PrepareForGetJobInputActions(roundTripper *mhttp.MockRoundTripper, rootURL, user, passwd, jobName string, buildID int) (
request *http.Request, response *http.Response) {
request, _ = http.NewRequest("GET", fmt.Sprintf("%s/job/%s/%d/wfapi/pendingInputActions", rootURL, jobName, buildID), nil)
response = &http.Response{
StatusCode: 200,
Request: request,
Body: ioutil.NopCloser(bytes.NewBufferString(`
[{"id":"Eff7d5dba32b4da32d9a67a519434d3f","proceedText":"继续","message":"message","inputs":[],
"proceedUrl":"/job/test/5/wfapi/inputSubmit?inputId=Eff7d5dba32b4da32d9a67a519434d3f",
"abortUrl":"/job/test/5/input/Eff7d5dba32b4da32d9a67a519434d3f/abort","redirectApprovalUrl":"/job/test/5/input/"}]`)),
}
roundTripper.EXPECT().
RoundTrip(request).Return(response, nil)
if user != "" && passwd != "" {
request.SetBasicAuth(user, passwd)
}
return
}
// PrepareForSubmitInput only for test
func PrepareForSubmitInput(roundTripper *mhttp.MockRoundTripper, rootURL, jobPath, user, passwd string) (
request *http.Request, response *http.Response) {
request, _ = http.NewRequest("POST", fmt.Sprintf("%s%s/%d/input/%s/abort", rootURL, jobPath, 1, "Eff7d5dba32b4da32d9a67a519434d3f"), nil)
PrepareCommonPost(request, roundTripper, user, passwd, rootURL)
return
}
// PrepareForSubmitProcessInput only for test
func PrepareForSubmitProcessInput(roundTripper *mhttp.MockRoundTripper, rootURL, jobPath, user, passwd string) (
request *http.Request, response *http.Response) {
request, _ = http.NewRequest("POST", fmt.Sprintf("%s%s/%d/input/%s/proceed", rootURL, jobPath, 1, "Eff7d5dba32b4da32d9a67a519434d3f"), nil)
PrepareCommonPost(request, roundTripper, user, passwd, rootURL)
return
}
......@@ -12,8 +12,6 @@ import (
"path/filepath"
"strings"
"github.com/jenkins-zh/jenkins-cli/util"
"github.com/jenkins-zh/jenkins-cli/mock/mhttp"
)
......@@ -509,7 +507,7 @@ func PrepareForDeleteUser(roundTripper *mhttp.MockRoundTripper, rootURL, userNam
// PrepareCommonPost only for test
func PrepareCommonPost(request *http.Request, roundTripper *mhttp.MockRoundTripper, user, passwd, rootURL string) {
request.Header.Add("CrumbRequestField", "Crumb")
request.Header.Add(util.ContentType, util.ApplicationForm)
// request.Header.Add(util.ContentType, util.ApplicationForm)
response := &http.Response{
StatusCode: 200,
Proto: "HTTP/1.1",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册