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

Add support to search Jenkins job or folder (#281)

* Add support to search Jenkins job or folder

* Fix the comments

* Add start and limit support for the list API

* Bump the dep version of pipeline-restful-api

* Enable output jobs without headers
上级 b82c9862
package cmd
import (
"fmt"
"github.com/jenkins-zh/jenkins-cli/client"
"github.com/jenkins-zh/jenkins-cli/app/i18n"
"github.com/spf13/cobra"
)
......@@ -24,27 +20,10 @@ var cascOptions CASCOptions
// Check do the health check of casc cmd
func (o *CASCOptions) Check() (err error) {
jClient := &client.PluginManager{
JenkinsCore: client.JenkinsCore{
RoundTripper: cascOptions.RoundTripper,
},
}
getCurrentJenkinsAndClient(&(jClient.JenkinsCore))
support := false
var plugins *client.InstalledPluginList
if plugins, err = jClient.GetPlugins(1); err == nil {
for _, plugin := range plugins.Plugins {
if plugin.ShortName == "configuration-as-code" {
support = true
break
}
}
}
if !support {
err = fmt.Errorf(i18n.T("lack of plugin configuration-as-code"))
opt := PluginOptions{
CommonOption: CommonOption{RoundTripper: o.RoundTripper},
}
_, err = opt.FindPlugin("configuration-as-code")
return
}
......
package cmd
import (
"bytes"
"fmt"
"github.com/jenkins-zh/jenkins-cli/app/i18n"
"github.com/jenkins-zh/jenkins-cli/util"
"net/http"
"strings"
"github.com/jenkins-zh/jenkins-cli/app/helper"
"github.com/hashicorp/go-version"
"github.com/jenkins-zh/jenkins-cli/client"
"github.com/spf13/cobra"
)
......@@ -15,8 +15,10 @@ import (
// JobSearchOption is the options of job search command
type JobSearchOption struct {
OutputOption
PrintAll bool
Max int
Start int
Limit int
Name string
Type string
RoundTripper http.RoundTripper
}
......@@ -25,85 +27,90 @@ var jobSearchOption JobSearchOption
func init() {
jobCmd.AddCommand(jobSearchCmd)
jobSearchCmd.Flags().IntVarP(&jobSearchOption.Max, "max", "", 10,
i18n.T("The number of limitation to print"))
jobSearchCmd.Flags().BoolVarP(&jobSearchOption.PrintAll, "all", "", false,
i18n.T("Print all items if there's no keyword"))
jobSearchCmd.Flags().StringVarP(&jobSearchOption.Format, "output", "o", "json",
i18n.T(`Formats of the output which contain name, path`))
jobSearchCmd.Flags().IntVarP(&jobSearchOption.Start, "start", "", 0,
i18n.T("The list of items offset"))
jobSearchCmd.Flags().IntVarP(&jobSearchOption.Limit, "limit", "", 50,
i18n.T("The list of items limit"))
jobSearchCmd.Flags().StringVarP(&jobSearchOption.Name, "name", "", "",
i18n.T("The name of plugin for search"))
jobSearchCmd.Flags().StringVarP(&jobSearchOption.Type, "type", "", "",
i18n.T("The type of plugin for search"))
jobSearchOption.SetFlag(jobSearchCmd)
healthCheckRegister.Register(getCmdPath(jobSearchCmd), &jobSearchOption)
}
var jobSearchCmd = &cobra.Command{
Use: "search [keyword]",
Short: i18n.T("Print the job of your Jenkins"),
Long: i18n.T(`Print the job of your Jenkins`),
Run: func(cmd *cobra.Command, args []string) {
if !jobSearchOption.PrintAll && len(args) == 0 {
cmd.Help()
return
}
if jobSearchOption.PrintAll && len(args) == 0 {
args = []string{""}
Use: "search",
Short: i18n.T("Print the job of your Jenkins"),
Long: i18n.T(`Print the job of your Jenkins`),
Example: "jcli job search [keyword] --name keyword --type Folder",
PreRun: func(cmd *cobra.Command, args []string) {
if len(args) > 0 {
jobSearchOption.Name = args[0]
}
keyword := args[0]
jclient := &client.JobClient{
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
jClient := &client.JobClient{
JenkinsCore: client.JenkinsCore{
RoundTripper: jobSearchOption.RoundTripper,
},
}
getCurrentJenkinsAndClientOrDie(&(jclient.JenkinsCore))
getCurrentJenkinsAndClient(&(jClient.JenkinsCore))
status, err := jclient.Search(keyword, jobSearchOption.Max)
if err == nil {
var items []client.JenkinsItem
if items, err = jClient.Search(jobSearchOption.Name, jobSearchOption.Type,
jobSearchOption.Start, jobSearchOption.Limit); err == nil {
var data []byte
data, err = jobSearchOption.Output(status)
if err == nil {
cmd.Println(string(data))
if data, err = jobSearchOption.Output(items); err == nil {
cmd.Print(string(data))
}
}
helper.CheckErr(cmd, err)
return
},
}
// Output render data into byte array
func (o *JobSearchOption) Output(obj interface{}) (data []byte, err error) {
if data, err = o.OutputOption.Output(obj); err != nil {
var formatFunc JobNameFormat
switch o.OutputOption.Format {
case "name":
formatFunc = simpleFormat
case "path":
formatFunc = pathFormat
}
if formatFunc == nil {
err = fmt.Errorf("unknow format %s", o.OutputOption.Format)
return
}
buf := ""
searchResult := obj.(*client.SearchResult)
for _, item := range searchResult.Suggestions {
buf = fmt.Sprintf("%s%s\n", buf, formatFunc(item.Name))
items := obj.([]client.JenkinsItem)
buf := new(bytes.Buffer)
table := util.CreateTableWithHeader(buf, o.WithoutHeaders)
table.AddRow("number", "name", "displayname", "type", "url")
for i, item := range items {
table.AddRow(fmt.Sprintf("%d", i), item.Name, item.DisplayName, item.Type, item.URL)
}
data = []byte(strings.Trim(buf, "\n"))
table.Render()
data = buf.Bytes()
err = nil
}
return
}
// JobNameFormat format the job name
type JobNameFormat func(string) string
func simpleFormat(name string) string {
return name
}
// Check do the conditions check
func (o *JobSearchOption) Check() (err error) {
opt := PluginOptions{
CommonOption: CommonOption{RoundTripper: o.RoundTripper},
}
const pluginName = "pipeline-restful-api"
const targetVersion = "0.3"
var plugin *client.InstalledPlugin
if plugin, err = opt.FindPlugin(pluginName); err == nil {
var (
current *version.Version
target *version.Version
versionMatch bool
)
if current, err = version.NewVersion(plugin.Version); err == nil {
if target, err = version.NewVersion(targetVersion); err == nil {
versionMatch = current.GreaterThanOrEqual(target)
}
}
func pathFormat(name string) string {
return client.ParseJobPath(name)
if err == nil && !versionMatch {
err = fmt.Errorf("%s version is %s, should be %s", pluginName, plugin.Version, targetVersion)
}
}
return
}
......@@ -2,17 +2,14 @@ package cmd
import (
"bytes"
"fmt"
"github.com/jenkins-zh/jenkins-cli/client"
"io/ioutil"
"net/http"
"os"
"github.com/golang/mock/gomock"
"github.com/jenkins-zh/jenkins-cli/mock/mhttp"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/spf13/cobra"
"github.com/jenkins-zh/jenkins-cli/mock/mhttp"
)
var _ = Describe("job search command", func() {
......@@ -36,27 +33,6 @@ var _ = Describe("job search command", func() {
ctrl.Finish()
})
Context("no need roundtripper", func() {
It("search without any options", func() {
data, err := generateSampleConfig()
Expect(err).To(BeNil())
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
jobSearchCmd.SetHelpFunc(func(cmd *cobra.Command, _ []string) {
cmd.Print("help")
})
rootCmd.SetArgs([]string{"job", "search"})
buf := new(bytes.Buffer)
rootCmd.SetOutput(buf)
_, err = rootCmd.ExecuteC()
Expect(err).To(BeNil())
Expect(buf.String()).To(Equal("help"))
})
})
Context("basic cases, need roundtrippper", func() {
BeforeEach(func() {
roundTripper = mhttp.NewMockRoundTripper(ctrl)
......@@ -69,33 +45,21 @@ var _ = Describe("job search command", func() {
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
keyword := "fake"
name := "fake"
kind := "fake"
request, _ := http.NewRequest("GET", fmt.Sprintf("http://localhost:8080/jenkins/search/suggest?query=%s&max=10", keyword), nil)
request.SetBasicAuth("admin", "111e3a2f0231198855dceaff96f20540a9")
response := &http.Response{
StatusCode: 200,
Proto: "HTTP/1.1",
Request: request,
Body: ioutil.NopCloser(bytes.NewBufferString(`{"suggestions": [{"name": "fake"}]}`)),
}
roundTripper.EXPECT().
RoundTrip(request).Return(response, nil)
client.PrepareOneItem(roundTripper, "http://localhost:8080/jenkins", name, kind,
"admin", "111e3a2f0231198855dceaff96f20540a9")
rootCmd.SetArgs([]string{"job", "search", keyword})
rootCmd.SetArgs([]string{"job", "search", name, "--type", kind})
buf := new(bytes.Buffer)
rootCmd.SetOutput(buf)
_, err = rootCmd.ExecuteC()
Expect(err).To(BeNil())
Expect(buf.String()).To(Equal(`{
"Suggestions": [
{
"Name": "fake"
}
]
}
Expect(buf.String()).To(Equal(`number name displayname type url
0 fake fake WorkflowJob job/fake/
`))
})
......@@ -105,61 +69,91 @@ var _ = Describe("job search command", func() {
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
request, _ := http.NewRequest("GET", "http://localhost:8080/jenkins/search/suggest?query=&max=10", nil)
request.SetBasicAuth("admin", "111e3a2f0231198855dceaff96f20540a9")
response := &http.Response{
StatusCode: 200,
Proto: "HTTP/1.1",
Request: request,
Body: ioutil.NopCloser(bytes.NewBufferString(`{"suggestions": [{"name": "fake"}]}`)),
}
roundTripper.EXPECT().
RoundTrip(request).Return(response, nil)
client.PrepareOneItem(roundTripper, "http://localhost:8080/jenkins", "", "",
"admin", "111e3a2f0231198855dceaff96f20540a9")
rootCmd.SetArgs([]string{"job", "search", "--all"})
rootCmd.SetArgs([]string{"job", "search", "--type=", "--name="})
buf := new(bytes.Buffer)
rootCmd.SetOutput(buf)
_, err = rootCmd.ExecuteC()
Expect(err).To(BeNil())
Expect(buf.String()).To(Equal(`{
"Suggestions": [
{
"Name": "fake"
}
]
}
Expect(buf.String()).To(Equal(`number name displayname type url
0 fake fake WorkflowJob job/fake/
`))
})
})
})
var _ = Describe("job search command check", func() {
var (
ctrl *gomock.Controller
roundTripper *mhttp.MockRoundTripper
rootURL string
user string
password string
)
It("should success, output format is name", func() {
BeforeEach(func() {
ctrl = gomock.NewController(GinkgoT())
roundTripper = mhttp.NewMockRoundTripper(ctrl)
jobSearchOption.RoundTripper = roundTripper
rootOptions.Jenkins = ""
rootOptions.ConfigFile = "test.yaml"
rootURL = "http://localhost:8080/jenkins"
user = "admin"
password = "111e3a2f0231198855dceaff96f20540a9"
})
AfterEach(func() {
rootCmd.SetArgs([]string{})
os.Remove(rootOptions.ConfigFile)
rootOptions.ConfigFile = ""
ctrl.Finish()
})
Context("basic cases", func() {
It("without pipeline-restful-api plugin", func() {
data, err := generateSampleConfig()
Expect(err).To(BeNil())
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
request, _ := http.NewRequest("GET", "http://localhost:8080/jenkins/search/suggest?query=&max=10", nil)
request.SetBasicAuth("admin", "111e3a2f0231198855dceaff96f20540a9")
response := &http.Response{
StatusCode: 200,
Proto: "HTTP/1.1",
Request: request,
Body: ioutil.NopCloser(bytes.NewBufferString(`{"suggestions": [{"name": "fake"},{"name": "fake1"}]}`)),
}
roundTripper.EXPECT().
RoundTrip(request).Return(response, nil)
req, _ := client.PrepareForOneInstalledPlugin(roundTripper, rootURL)
req.SetBasicAuth(user, password)
rootCmd.SetArgs([]string{"job", "search", "--all", "-o", "name"})
err = jobSearchOption.Check()
Expect(err).To(HaveOccurred())
})
buf := new(bytes.Buffer)
rootCmd.SetOutput(buf)
_, err = rootCmd.ExecuteC()
It("with pipeline-restful-api 0.2+ plugin", func() {
data, err := generateSampleConfig()
Expect(err).To(BeNil())
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
Expect(buf.String()).To(Equal(`fake
fake1
`))
req, _ := client.PrepareForOneInstalledPluginWithPluginNameAndVer(roundTripper, rootURL,
"pipeline-restful-api", "1.0")
req.SetBasicAuth(user, password)
err = jobSearchOption.Check()
Expect(err).NotTo(HaveOccurred())
})
It("with pipeline-restful-api 0.2- plugin", func() {
data, err := generateSampleConfig()
Expect(err).To(BeNil())
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
req, _ := client.PrepareForOneInstalledPluginWithPluginNameAndVer(roundTripper, rootURL,
"pipeline-restful-api", "0.1")
req.SetBasicAuth(user, password)
err = jobSearchOption.Check()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("should be"))
})
})
})
package cmd
import (
"fmt"
"github.com/jenkins-zh/jenkins-cli/app/i18n"
"github.com/jenkins-zh/jenkins-cli/client"
"github.com/spf13/cobra"
)
// PluginOptions contains the command line options
type PluginOptions struct {
CommonOption
Suite string
}
......@@ -24,3 +28,17 @@ var pluginCmd = &cobra.Command{
jcli plugin search github
jcli plugin check`,
}
// FindPlugin find a plugin by name
func (o *PluginOptions) FindPlugin(name string) (plugin *client.InstalledPlugin, err error) {
jClient := &client.PluginManager{
JenkinsCore: client.JenkinsCore{
RoundTripper: o.RoundTripper,
},
}
getCurrentJenkinsAndClient(&(jClient.JenkinsCore))
if plugin, err = jClient.FindInstalledPlugin(name); err == nil && plugin == nil {
err = fmt.Errorf(fmt.Sprintf("lack of plugin %s", name))
}
return
}
......@@ -104,6 +104,7 @@ func (o *RootOptions) RunDiagnose(cmd *cobra.Command) (err error) {
return
}
path := getCmdPath(cmd)
logger.Debug("start to run diagnose", zap.String("path", path))
for k, v := range healthCheckRegister.Member {
if ok, _ := regexp.MatchString(k, path); ok {
......
......@@ -18,8 +18,10 @@ type JobClient struct {
}
// Search find a set of jobs by name
func (q *JobClient) Search(keyword string, max int) (status *SearchResult, err error) {
err = q.RequestWithData("GET", fmt.Sprintf("/search/suggest?query=%s&max=%d", keyword, max), nil, nil, 200, &status)
func (q *JobClient) Search(name, kind string, start, limit int) (items []JenkinsItem, err error) {
err = q.RequestWithData("GET", fmt.Sprintf("/items/list?name=%s&type=%s&start=%d&limit=%d",
name, kind, start, limit),
nil, nil, 200, &items)
return
}
......@@ -304,14 +306,13 @@ type JobLog struct {
Text string
}
// SearchResult holds the result items
type SearchResult struct {
Suggestions []SearchResultItem
}
// SearchResultItem hold the result item
type SearchResultItem struct {
Name string
// JenkinsItem represents the item of Jenkins
type JenkinsItem struct {
Name string
DisplayName string
URL string
Description string
Type string
}
// Job represents a job
......
......@@ -34,42 +34,28 @@ var _ = Describe("job test", func() {
Context("Search", func() {
It("basic case with one result item", func() {
keyword := "fake"
name := "fake"
kind := "fake"
request, _ := http.NewRequest("GET", fmt.Sprintf("%s%s%s&max=1", jobClient.URL, "/search/suggest?query=", keyword), nil)
response := &http.Response{
StatusCode: 200,
Proto: "HTTP/1.1",
Request: request,
Body: ioutil.NopCloser(bytes.NewBufferString(`{"suggestions": [{"name": "fake"}]}`)),
}
roundTripper.EXPECT().
RoundTrip(request).Return(response, nil)
PrepareOneItem(roundTripper, jobClient.URL, name, kind, "", "")
result, err := jobClient.Search(keyword, 1)
result, err := jobClient.Search(name, kind, 0, 50)
Expect(err).To(BeNil())
Expect(result).NotTo(BeNil())
Expect(len(result.Suggestions)).To(Equal(1))
Expect(result.Suggestions[0].Name).To(Equal("fake"))
Expect(len(result)).To(Equal(1))
Expect(result[0].Name).To(Equal("fake"))
})
It("basic case without any result items", func() {
keyword := "fake"
name := "fake"
kind := "fake"
request, _ := http.NewRequest("GET", fmt.Sprintf("%s%s%s&max=1", jobClient.URL, "/search/suggest?query=", keyword), nil)
response := &http.Response{
StatusCode: 200,
Proto: "HTTP/1.1",
Request: request,
Body: ioutil.NopCloser(bytes.NewBufferString(`{"suggestions":[]}`)),
}
roundTripper.EXPECT().
RoundTrip(request).Return(response, nil)
PrepareEmptyItems(roundTripper, jobClient.URL, name, kind, "", "")
result, err := jobClient.Search(keyword, 1)
result, err := jobClient.Search(name, kind, 0, 50)
Expect(err).To(BeNil())
Expect(result).NotTo(BeNil())
Expect(len(result.Suggestions)).To(Equal(0))
Expect(len(result)).To(Equal(0))
})
})
......
......@@ -185,3 +185,35 @@ func PrepareForJobLog(roundTripper *mhttp.MockRoundTripper, rootURL, jobName str
request.SetBasicAuth(user, password)
}
}
// PrepareOneItem only for test
func PrepareOneItem(roundTripper *mhttp.MockRoundTripper, rootURL, name, kind, user, token string) {
request, _ := http.NewRequest("GET", fmt.Sprintf("%s/items/list?name=%s&type=%s&start=%d&limit=%d",
rootURL, name, kind, 0, 50), nil)
response := &http.Response{
StatusCode: 200,
Request: request,
Body: ioutil.NopCloser(bytes.NewBufferString(`[{"name":"fake","displayName":"fake","description":null,"type":"WorkflowJob","shortURL":"job/fake/","url":"job/fake/"}]`)),
}
roundTripper.EXPECT().
RoundTrip(request).Return(response, nil)
if user != "" && token != "" {
request.SetBasicAuth(user, token)
}
}
// PrepareEmptyItems only for test
func PrepareEmptyItems(roundTripper *mhttp.MockRoundTripper, rootURL, name, kind, user, token string) {
request, _ := http.NewRequest("GET", fmt.Sprintf("%s/items/list?name=%s&type=%s&start=%d&limit=%d",
rootURL, name, kind, 0, 50), nil)
response := &http.Response{
StatusCode: 200,
Request: request,
Body: ioutil.NopCloser(bytes.NewBufferString(`[]`)),
}
roundTripper.EXPECT().
RoundTrip(request).Return(response, nil)
if user != "" && token != "" {
request.SetBasicAuth(user, token)
}
}
......@@ -11,16 +11,24 @@ import (
// PrepareForOneInstalledPluginWithPluginName only for test
func PrepareForOneInstalledPluginWithPluginName(roundTripper *mhttp.MockRoundTripper, rootURL, pluginName string) (
request *http.Request, response *http.Response) {
request, response = PrepareForOneInstalledPluginWithPluginNameAndVer(roundTripper, rootURL, pluginName, "1.0")
return
}
// PrepareForOneInstalledPluginWithPluginNameAndVer only for test
func PrepareForOneInstalledPluginWithPluginNameAndVer(roundTripper *mhttp.MockRoundTripper, rootURL,
pluginName, version string) (
request *http.Request, response *http.Response) {
request, response = PrepareForEmptyInstalledPluginList(roundTripper, rootURL, 1)
response.Body = ioutil.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{
"plugins": [{
"shortName": "%s",
"version": "1.0",
"version": "%s",
"hasUpdate": true,
"enable": true,
"active": true
}]
}`, pluginName)))
}`, pluginName, version)))
return
}
......@@ -101,6 +101,20 @@ func (p *PluginManager) GetPlugins(depth int) (pluginList *InstalledPluginList,
return
}
// FindInstalledPlugin find the exist plugin by name
func (p *PluginManager) FindInstalledPlugin(name string) (targetPlugin *InstalledPlugin, err error) {
var plugins *InstalledPluginList
if plugins, err = p.GetPlugins(1); err == nil {
for _, plugin := range plugins.Plugins {
if plugin.ShortName == name {
targetPlugin = &plugin
break
}
}
}
return
}
func (p *PluginManager) getPluginsInstallQuery(names []string) string {
pluginNames := make([]string, 0)
for _, name := range names {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册