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

Add post command hook (#140)

* Add post command hook

* Remove commented codes

* Fix the reduandent codes
上级 a67c3949
......@@ -99,6 +99,12 @@ func (b *InteractiveOption) SetFlag(cmd *cobra.Command) {
cmd.Flags().BoolVarP(&b.Interactive, "interactive", "i", false, "Interactive mode")
}
// HookOption is the option whether skip command hook
type HookOption struct {
SkipPreHook bool
SkipPostHook bool
}
func getCurrentJenkinsAndClient(jclient *client.JenkinsCore) (jenkins *JenkinsServer) {
jenkins = getCurrentJenkinsFromOptionsOrDie()
jclient.URL = jenkins.URL
......
......@@ -50,7 +50,8 @@ type JenkinsServer struct {
Description string `yaml:"description"`
}
type PreHook struct {
// CommndHook is a hook
type CommndHook struct {
Path string `yaml:"path"`
Command string `yaml:"cmd"`
}
......@@ -62,10 +63,12 @@ type PluginSuite struct {
Description string `yaml:"description"`
}
// Config is a global config struct
type Config struct {
Current string `yaml:"current"`
JenkinsServers []JenkinsServer `yaml:"jenkins_servers"`
PreHooks []PreHook `yaml:"preHooks"`
PreHooks []CommndHook `yaml:"preHooks"`
PostHooks []CommndHook `yaml:"postHooks"`
PluginSuites []PluginSuite `yaml:"pluginSuites"`
}
......
......@@ -4,6 +4,7 @@ import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"strings"
......@@ -19,6 +20,11 @@ type PluginUploadOption struct {
RemoteUser string
RemotePassword string
RemoteJenkins string
ShowProgress bool
RoundTripper http.RoundTripper
HookOption
pluginFilePath string
}
......@@ -27,10 +33,14 @@ var pluginUploadOption PluginUploadOption
func init() {
pluginCmd.AddCommand(pluginUploadCmd)
pluginUploadCmd.Flags().BoolVarP(&pluginUploadOption.ShowProgress, "show-progress", "", true, "Whether show the upload progress")
pluginUploadCmd.Flags().StringVarP(&pluginUploadOption.Remote, "remote", "r", "", "Remote plugin URL")
pluginUploadCmd.Flags().StringVarP(&pluginUploadOption.RemoteUser, "remote-user", "", "", "User of remote plugin URL")
pluginUploadCmd.Flags().StringVarP(&pluginUploadOption.RemotePassword, "remote-password", "", "", "Password of remote plugin URL")
pluginUploadCmd.Flags().StringVarP(&pluginUploadOption.RemoteJenkins, "remote-jenkins", "", "", "Remote Jenkins which will find from config list")
pluginUploadCmd.Flags().BoolVarP(&pluginUploadOption.SkipPreHook, "skip-prehook", "", false, "Whether skip the previous command hook")
pluginUploadCmd.Flags().BoolVarP(&pluginUploadOption.SkipPostHook, "skip-posthook", "", false, "Whether skip the post command hook")
}
var pluginUploadCmd = &cobra.Command{
......@@ -39,7 +49,8 @@ var pluginUploadCmd = &cobra.Command{
Short: "Upload a plugin to your Jenkins",
Long: `Upload a plugin from local filesystem or remote URL to your Jenkins`,
Example: ` jcli plugin upload --remote https://server/sample.hpi
jcli plugin upload sample.hpi`,
jcli plugin upload sample.hpi
jcli plugin upload sample.hpi --show-progress=false`,
PreRun: func(cmd *cobra.Command, args []string) {
if pluginUploadOption.Remote != "" {
file, err := ioutil.TempFile(".", "jcli-plugin")
......@@ -70,7 +81,9 @@ var pluginUploadCmd = &cobra.Command{
log.Fatal(err)
}
} else if len(args) == 0 {
executePreCmd(cmd, args, os.Stdout)
if !pluginUploadOption.SkipPreHook {
executePreCmd(cmd, args, os.Stdout)
}
path, _ := os.Getwd()
dirName := filepath.Base(path)
......@@ -82,14 +95,22 @@ var pluginUploadCmd = &cobra.Command{
pluginUploadOption.pluginFilePath = args[0]
}
},
Run: func(_ *cobra.Command, _ []string) {
jenkins := getCurrentJenkinsFromOptionsOrDie()
jclient := &client.PluginManager{}
jclient.URL = jenkins.URL
jclient.UserName = jenkins.UserName
jclient.Token = jenkins.Token
jclient.Proxy = jenkins.Proxy
jclient.ProxyAuth = jenkins.ProxyAuth
PostRun: func(cmd *cobra.Command, args []string) {
if pluginUploadOption.SkipPostHook {
return
}
executePostCmd(cmd, args, cmd.OutOrStdout())
},
Run: func(cmd *cobra.Command, _ []string) {
jclient := &client.PluginManager{
JenkinsCore: client.JenkinsCore{
RoundTripper: pluginUploadOption.RoundTripper,
Output: cmd.OutOrStdout(),
},
ShowProgress: pluginUploadOption.ShowProgress,
}
getCurrentJenkinsAndClient(&(jclient.JenkinsCore))
jclient.Debug = rootOptions.Debug
if pluginUploadOption.Remote != "" {
......
package cmd
import (
"bytes"
"io/ioutil"
"os"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/jenkins-zh/jenkins-cli/client"
"github.com/jenkins-zh/jenkins-cli/mock/mhttp"
)
var _ = Describe("plugin upload command", func() {
var (
ctrl *gomock.Controller
roundTripper *mhttp.MockRoundTripper
)
BeforeEach(func() {
ctrl = gomock.NewController(GinkgoT())
roundTripper = mhttp.NewMockRoundTripper(ctrl)
pluginUploadOption.RoundTripper = roundTripper
rootCmd.SetArgs([]string{})
rootOptions.Jenkins = ""
rootOptions.ConfigFile = "test.yaml"
})
AfterEach(func() {
rootCmd.SetArgs([]string{})
os.Remove(rootOptions.ConfigFile)
rootOptions.ConfigFile = ""
ctrl.Finish()
})
Context("basic cases", func() {
It("should success", func() {
data, err := generateSampleConfig()
Expect(err).To(BeNil())
err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664)
Expect(err).To(BeNil())
tmpfile, err := ioutil.TempFile("", "example")
Expect(err).To(BeNil())
request, _, requestCrumb, _ := client.PrepareForUploadPlugin(roundTripper, "http://localhost:8080/jenkins")
request.SetBasicAuth("admin", "111e3a2f0231198855dceaff96f20540a9")
requestCrumb.SetBasicAuth("admin", "111e3a2f0231198855dceaff96f20540a9")
rootCmd.SetArgs([]string{"plugin", "upload", tmpfile.Name(), "--show-progress=false"})
buf := new(bytes.Buffer)
rootCmd.SetOutput(buf)
_, err = rootCmd.ExecuteC()
Expect(err).To(BeNil())
Expect(buf.String()).To(Equal(""))
})
})
})
......@@ -120,12 +120,32 @@ func executePreCmd(cmd *cobra.Command, _ []string, writer io.Writer) (err error)
}
path := getCmdPath(cmd)
for _, preHook := range config.PreHooks {
if path != preHook.Path {
for _, hook := range config.PreHooks {
if path != hook.Path {
continue
}
if err = execute(preHook.Command, writer); err != nil {
if err = execute(hook.Command, writer); err != nil {
return
}
}
return
}
func executePostCmd(cmd *cobra.Command, _ []string, writer io.Writer) (err error) {
config := getConfig()
if config == nil {
err = fmt.Errorf("Cannot find config file")
return
}
path := getCmdPath(cmd)
for _, hook := range config.PostHooks {
if path != hook.Path {
continue
}
if err = execute(hook.Command, writer); err != nil {
return
}
}
......
......@@ -86,7 +86,7 @@ var _ = Describe("Root cmd test", func() {
It("basic use case with one preHook, should success", func() {
config = &Config{
PreHooks: []PreHook{PreHook{
PreHooks: []CommndHook{CommndHook{
Path: "test",
Command: successCmd,
}},
......@@ -106,13 +106,13 @@ var _ = Describe("Root cmd test", func() {
It("basic use case with many preHooks, should success", func() {
config = &Config{
PreHooks: []PreHook{PreHook{
PreHooks: []CommndHook{CommndHook{
Path: "test",
Command: successCmd,
}, PreHook{
}, CommndHook{
Path: "test",
Command: "echo 2",
}, PreHook{
}, CommndHook{
Path: "fake",
Command: successCmd,
}},
......@@ -147,7 +147,7 @@ var _ = Describe("Root cmd test", func() {
It("basic use case with error command, should success", func() {
config = &Config{
PreHooks: []PreHook{PreHook{
PreHooks: []CommndHook{CommndHook{
Path: "test",
Command: errorCmd,
}},
......
......@@ -22,6 +22,7 @@ type JenkinsCore struct {
ProxyAuth string
Debug bool
Output io.Writer
RoundTripper http.RoundTripper
}
......@@ -112,6 +113,48 @@ func (j *JenkinsCore) GetCrumb() (crumbIssuer *JenkinsCrumb, err error) {
return
}
// RequestWithData requests the api and parse the data into an interface
func (j *JenkinsCore) RequestWithData(method, api string, headers map[string]string,
payload io.Reader, successCode int, obj interface{}) (err error) {
var (
statusCode int
data []byte
)
if statusCode, data, err = j.Request(method, api, headers, payload); err == nil {
if statusCode == successCode {
json.Unmarshal(data, obj)
} else {
err = j.ErrorHandle(statusCode, data)
}
}
return
}
// RequestWithoutData requests the api without handling data
func (j *JenkinsCore) RequestWithoutData(method, api string, headers map[string]string,
payload io.Reader, successCode int) (err error) {
var (
statusCode int
data []byte
)
if statusCode, data, err = j.Request(method, api, headers, payload); err == nil &&
statusCode != successCode {
err = j.ErrorHandle(statusCode, data)
}
return
}
// ErrorHandle handles the error cases
func (j *JenkinsCore) ErrorHandle(statusCode int, data []byte) (err error) {
err = fmt.Errorf("unexpected status code: %d", statusCode)
if j.Debug {
ioutil.WriteFile("debug.html", data, 0664)
}
return
}
// Request make a common request
func (j *JenkinsCore) Request(method, api string, headers map[string]string, payload io.Reader) (
statusCode int, data []byte, err error) {
......
......@@ -20,40 +20,14 @@ type JobClient struct {
// Search find a set of jobs by name
func (q *JobClient) Search(keyword string) (status *SearchResult, err error) {
var (
statusCode int
data []byte
)
if statusCode, data, err = q.Request("GET", fmt.Sprintf("/search/suggest?query=%s", keyword), nil, nil); err == nil {
if statusCode == 200 {
json.Unmarshal(data, &status)
} else {
err = fmt.Errorf("unexpected status code: %d", statusCode)
}
}
err = q.RequestWithData("GET", fmt.Sprintf("/search/suggest?query=%s", keyword), nil, nil, 200, &status)
return
}
// Build trigger a job
func (q *JobClient) Build(jobName string) (err error) {
path := parseJobPath(jobName)
var (
statusCode int
data []byte
)
if statusCode, data, err = q.Request("POST", fmt.Sprintf("%s/build", path), nil, nil); err == nil {
if statusCode == 201 {
fmt.Println("build successfully")
} else {
err = fmt.Errorf("unexpected status code: %d", statusCode)
if q.Debug {
ioutil.WriteFile("debug.html", data, 0664)
}
}
}
err = q.RequestWithoutData("POST", fmt.Sprintf("%s/build", path), nil, nil, 201)
return
}
......@@ -67,22 +41,7 @@ func (q *JobClient) GetBuild(jobName string, id int) (job *JobBuild, err error)
api = fmt.Sprintf("%s/%d/api/json", path, id)
}
var (
statusCode int
data []byte
)
if statusCode, data, err = q.Request("GET", api, nil, nil); err == nil {
if statusCode == 200 {
job = &JobBuild{}
err = json.Unmarshal(data, job)
} else {
err = fmt.Errorf("unexpected status code: %d", statusCode)
if q.Debug {
ioutil.WriteFile("debug.html", data, 0664)
}
}
}
err = q.RequestWithData("GET", api, nil, nil, 200, &job)
return
}
......@@ -139,21 +98,8 @@ func (q *JobClient) BuildWithParams(jobName string, parameters []ParameterDefini
func (q *JobClient) StopJob(jobName string, num int) (err error) {
path := parseJobPath(jobName)
api := fmt.Sprintf("%s/%d/stop", path, num)
var (
statusCode int
data []byte
)
if statusCode, data, err = q.Request("POST", api, nil, nil); err == nil {
if statusCode == 200 {
fmt.Println("stoped successfully")
} else {
err = fmt.Errorf("unexpected status code: %d", statusCode)
if q.Debug {
ioutil.WriteFile("debug.html", data, 0664)
}
}
}
err = q.RequestWithoutData("POST", api, nil, nil, 200)
return
}
......@@ -161,22 +107,8 @@ func (q *JobClient) StopJob(jobName string, num int) (err error) {
func (q *JobClient) GetJob(name string) (job *Job, err error) {
path := parseJobPath(name)
api := fmt.Sprintf("%s/api/json", path)
var (
statusCode int
data []byte
)
if statusCode, data, err = q.Request("GET", api, nil, nil); err == nil {
if statusCode == 200 {
job = &Job{}
err = json.Unmarshal(data, job)
} else {
err = fmt.Errorf("unexpected status code: %d", statusCode)
if q.Debug {
ioutil.WriteFile("debug.html", data, 0664)
}
}
}
err = q.RequestWithData("GET", api, nil, nil, 200, &job)
return
}
......
......@@ -134,4 +134,15 @@ var _ = Describe("PluginManager test", func() {
Expect(err).To(HaveOccurred())
})
})
Context("Upload", func() {
It("normal case, should success", func() {
tmpfile, err := ioutil.TempFile("", "example")
Expect(err).To(BeNil())
PrepareForUploadPlugin(roundTripper, pluginMgr.URL)
pluginMgr.Upload(tmpfile.Name())
})
})
})
......@@ -2,7 +2,6 @@ package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
......@@ -16,8 +15,11 @@ import (
"github.com/jenkins-zh/jenkins-cli/util"
)
// PluginManager is the client of plugin manager
type PluginManager struct {
JenkinsCore
ShowProgress bool
}
type Plugin struct {
......@@ -89,43 +91,13 @@ func (p *PluginManager) CheckUpdate(handle func(*http.Response)) {
// GetAvailablePlugins get the aviable plugins from Jenkins
func (p *PluginManager) GetAvailablePlugins() (pluginList *AvailablePluginList, err error) {
var (
statusCode int
data []byte
)
if statusCode, data, err = p.Request("GET", "/pluginManager/plugins", nil, nil); err == nil {
if statusCode == 200 {
pluginList = &AvailablePluginList{}
err = json.Unmarshal(data, pluginList)
} else {
err = fmt.Errorf("unexpected status code: %d", statusCode)
if p.Debug {
ioutil.WriteFile("debug.html", data, 0664)
}
}
}
err = p.RequestWithData("GET", "/pluginManager/plugins", nil, nil, 200, &pluginList)
return
}
// GetPlugins get installed plugins
func (p *PluginManager) GetPlugins() (pluginList *InstalledPluginList, err error) {
var (
statusCode int
data []byte
)
if statusCode, data, err = p.Request("GET", "/pluginManager/api/json?depth=1", nil, nil); err == nil {
if statusCode == 200 {
pluginList = &InstalledPluginList{}
err = json.Unmarshal(data, pluginList)
} else {
err = fmt.Errorf("unexpected status code: %d", statusCode)
if p.Debug {
ioutil.WriteFile("debug.html", data, 0664)
}
}
}
err = p.RequestWithData("GET", "/pluginManager/api/json?depth=1", nil, nil, 200, &pluginList)
return
}
......@@ -207,34 +179,27 @@ func (p *PluginManager) UninstallPlugin(name string) (err error) {
}
// Upload will upload a file from local filesystem into Jenkins
func (p *PluginManager) Upload(pluginFile string) {
func (p *PluginManager) Upload(pluginFile string) (err error) {
api := fmt.Sprintf("%s/pluginManager/uploadPlugin", p.URL)
extraParams := map[string]string{}
request, err := newfileUploadRequest(api, extraParams, "@name", pluginFile)
if err != nil {
log.Fatal(err)
}
if err == nil {
p.AuthHandle(request)
} else {
var request *http.Request
if request, err = p.newfileUploadRequest(api, extraParams, "@name", pluginFile); err != nil {
return
}
p.AuthHandle(request)
client := p.GetClient()
var response *http.Response
response, err = client.Do(request)
if err != nil {
log.Fatal(err)
if response, err = client.Do(request); err != nil {
return
} else if response.StatusCode != 200 {
fmt.Println("StatusCode", response.StatusCode)
var data []byte
if data, err = ioutil.ReadAll(response.Body); err == nil && p.Debug {
err = fmt.Errorf("StatusCode: %d", response.StatusCode)
if data, readErr := ioutil.ReadAll(response.Body); readErr == nil && p.Debug {
ioutil.WriteFile("debug.html", data, 0664)
} else {
log.Fatal(err)
}
}
return err
}
func (p *PluginManager) handleCheck(handle func(*http.Response)) func(*http.Response) {
......@@ -246,32 +211,28 @@ func (p *PluginManager) handleCheck(handle func(*http.Response)) func(*http.Resp
return handle
}
func newfileUploadRequest(uri string, params map[string]string, paramName, path string) (*http.Request, error) {
file, err := os.Open(path)
func (p *PluginManager) newfileUploadRequest(uri string, params map[string]string, paramName, path string) (req *http.Request, err error) {
var file *os.File
file, err = os.Open(path)
if err != nil {
return nil, err
return
}
var total float64
if stat, err := file.Stat(); err != nil {
panic(err)
} else {
total = float64(stat.Size())
var stat os.FileInfo
if stat, err = file.Stat(); err != nil {
return
}
total = float64(stat.Size())
defer file.Close()
bytesBuffer := &bytes.Buffer{}
progressWriter := &util.ProgressIndicator{
Total: total,
Writer: bytesBuffer,
Reader: bytesBuffer,
Title: "Uploading",
}
progressWriter.Init()
writer := multipart.NewWriter(bytesBuffer)
part, err := writer.CreateFormFile(paramName, filepath.Base(path))
var part io.Writer
part, err = writer.CreateFormFile(paramName, filepath.Base(path))
if err != nil {
return nil, err
return
}
_, err = io.Copy(part, file)
......@@ -281,10 +242,23 @@ func newfileUploadRequest(uri string, params map[string]string, paramName, path
}
err = writer.Close()
if err != nil {
return nil, err
return
}
var progressWriter *util.ProgressIndicator
if p.ShowProgress {
progressWriter = &util.ProgressIndicator{
Total: total,
Writer: bytesBuffer,
Reader: bytesBuffer,
Title: "Uploading",
}
progressWriter.Init()
req, err = http.NewRequest("POST", uri, progressWriter)
} else {
req, err = http.NewRequest("POST", uri, bytesBuffer)
}
req, err := http.NewRequest("POST", uri, progressWriter)
req.Header.Set("Content-Type", writer.FormDataContentType())
return req, err
return
}
package client
import (
"fmt"
"net/http"
)
type requestMatcher struct {
request *http.Request
verbose bool
matchOptions matchOptions
}
type matchOptions struct {
withQuery bool
}
// NewRequestMatcher create a request matcher will match request method and request path
func NewRequestMatcher(request *http.Request) *requestMatcher {
return &requestMatcher{request: request}
}
// NewVerboseRequestMatcher create a verbose request matcher will match request method and request path
func NewVerboseRequestMatcher(request *http.Request) *requestMatcher {
return &requestMatcher{request: request, verbose: true}
}
func (request *requestMatcher) WithQuery() *requestMatcher {
request.matchOptions.withQuery = true
return request
}
func (request *requestMatcher) Matches(x interface{}) bool {
target := x.(*http.Request)
if request.verbose {
fmt.Printf("%s=?%s , %s=?%s, %s=?%s \n", request.request.Method, target.Method, request.request.URL.Path, target.URL.Path,
request.request.URL.Opaque, target.URL.Opaque)
}
match := request.request.Method == target.Method && (request.request.URL.Path == target.URL.Path ||
request.request.URL.Path == target.URL.Opaque) //gitlab sdk did not set request path correctly
if request.matchOptions.withQuery {
if request.verbose {
fmt.Printf("%s=?%s \n", request.request.URL.RawQuery, target.URL.RawQuery)
}
match = match && (request.request.URL.RawQuery == target.URL.RawQuery)
}
return match
}
func (*requestMatcher) String() string {
return "request matcher"
}
......@@ -3,8 +3,11 @@ package client
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"path/filepath"
"github.com/jenkins-zh/jenkins-cli/mock/mhttp"
)
......@@ -89,6 +92,34 @@ func PrepareFor500InstalledPluginList(roundTripper *mhttp.MockRoundTripper, root
return
}
// PrepareForUploadPlugin only for test
func PrepareForUploadPlugin(roundTripper *mhttp.MockRoundTripper, rootURL string) (
request *http.Request, response *http.Response, requestCrumb *http.Request, responseCrumb *http.Response) {
tmpfile, _ := ioutil.TempFile("", "example")
bytesBuffer := &bytes.Buffer{}
writer := multipart.NewWriter(bytesBuffer)
part, _ := writer.CreateFormFile("@name", filepath.Base(tmpfile.Name()))
io.Copy(part, tmpfile)
request, _ = http.NewRequest("POST", fmt.Sprintf("%s/pluginManager/uploadPlugin", rootURL), nil)
request.Header.Add("CrumbRequestField", "Crumb")
request.Header.Set("Content-Type", writer.FormDataContentType())
response = &http.Response{
StatusCode: 200,
Proto: "HTTP/1.1",
Request: request,
Body: ioutil.NopCloser(bytes.NewBufferString("")),
}
roundTripper.EXPECT().
RoundTrip(NewRequestMatcher(request)).Return(response, nil)
// common crumb request
requestCrumb, responseCrumb = RequestCrumb(roundTripper, rootURL)
return
}
// PrepareForUninstallPlugin only for test
func PrepareForUninstallPlugin(roundTripper *mhttp.MockRoundTripper, rootURL, pluginName string) (
request *http.Request, response *http.Response, requestCrumb *http.Request, responseCrumb *http.Response) {
......@@ -101,7 +132,7 @@ func PrepareForUninstallPlugin(roundTripper *mhttp.MockRoundTripper, rootURL, pl
Body: ioutil.NopCloser(bytes.NewBufferString("")),
}
roundTripper.EXPECT().
RoundTrip(request).Return(response, nil)
RoundTrip(NewRequestMatcher(request)).Return(response, nil)
// common crumb request
requestCrumb, responseCrumb = RequestCrumb(roundTripper, rootURL)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册