diff --git a/app/cmd/config.go b/app/cmd/config.go index 1f5ca235fb8ad7da0420eecc126c6d904931d596..040670b1936fb2b20bdc2d961a7a63d55b93c103 100644 --- a/app/cmd/config.go +++ b/app/cmd/config.go @@ -22,14 +22,17 @@ func init() { } var configCmd = &cobra.Command{ - Use: "config", - Short: "Manage the config of jcli", - Long: `Manage the config of jcli`, + Use: "config", + Aliases: []string{"cfg"}, + Short: "Manage the config of jcli", + Long: `Manage the config of jcli`, Run: func(cmd *cobra.Command, args []string) { current := getCurrentJenkins() fmt.Printf("Current Jenkins's name is %s, url is %s\n", current.Name, current.URL) }, - Example: "jcli config -l", + Example: ` jcli config generate + jcli config list + jcli config edit`, } // JenkinsServer holds the configuration of your Jenkins diff --git a/app/cmd/config_generate.go b/app/cmd/config_generate.go index e74c5725f1827624c5535f2928ed12a7904721e8..b8e20b5de37a17e14c100e561b8ea9c651d0e469 100644 --- a/app/cmd/config_generate.go +++ b/app/cmd/config_generate.go @@ -24,9 +24,10 @@ func init() { } var configGenerateCmd = &cobra.Command{ - Use: "generate", - Short: "Generate a sample config file for you", - Long: `Generate a sample config file for you`, + Use: "generate", + Aliases: []string{"gen"}, + Short: "Generate a sample config file for you", + Long: `Generate a sample config file for you`, Run: func(cmd *cobra.Command, args []string) { if data, err := generateSampleConfig(); err == nil { configPath := configOptions.ConfigFileLocation diff --git a/app/cmd/plugin.go b/app/cmd/plugin.go index fe5898209cd6fb96e2504027a24f1ed4813fcd77..b1af456bdf6ad446046dc29ed72e8b38ac9d0c60 100644 --- a/app/cmd/plugin.go +++ b/app/cmd/plugin.go @@ -18,6 +18,9 @@ var pluginCmd = &cobra.Command{ Use: "plugin", Short: "Manage the plugins of Jenkins", Long: `Manage the plugins of Jenkins`, + Example: ` jcli plugin list + jcli plugin search github + jcli plugin check`, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/app/cmd/plugin_list.go b/app/cmd/plugin_list.go index 6570f327427d15602925308f01a708ab095962bc..8f9b25049e099a960274fd86cb967cbe1a795724 100644 --- a/app/cmd/plugin_list.go +++ b/app/cmd/plugin_list.go @@ -28,6 +28,8 @@ var pluginListCmd = &cobra.Command{ Use: "list", Short: "Print all the plugins which are installed", Long: `Print all the plugins which are installed`, + Example: ` jcli plugin list --filter name=github + jcli plugin list --filter hasUpdate`, Run: func(cmd *cobra.Command, args []string) { jenkins := getCurrentJenkins() jclient := &client.PluginManager{} diff --git a/app/cmd/plugin_upload.go b/app/cmd/plugin_upload.go index 37875ad5b8bf24b25a7e866ed345cbef44b3428a..293a43de4ef4f2c6176de8bb34f74fac2db5f020 100644 --- a/app/cmd/plugin_upload.go +++ b/app/cmd/plugin_upload.go @@ -1,18 +1,85 @@ package cmd import ( - "github.com/jenkins-zh/jenkins-cli/client" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + + "github.com/jenkins-zh/jenkins-cli/util" + "github.com/jenkins-zh/jenkins-cli/client" "github.com/spf13/cobra" ) +// PluginUploadOption will hold the options of plugin cmd +type PluginUploadOption struct { + Remote string + RemoteUser string + RemotePassword string + RemoteJenkins string + + pluginFilePath string +} + +var pluginUploadOption PluginUploadOption + func init() { pluginCmd.AddCommand(pluginUploadCmd) + 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") } var pluginUploadCmd = &cobra.Command{ - Use: "upload", - Short: "Upload the plugin from local to your Jenkins", - Long: `Upload the plugin from local to your Jenkins`, + Use: "upload", + Aliases: []string{"up"}, + 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`, + PreRun: func(cmd *cobra.Command, args []string) { + if pluginUploadOption.Remote != "" { + file, err := ioutil.TempFile(".", "jcli-plugin") + if err != nil { + log.Fatal(err) + } + + defer os.Remove(file.Name()) + + if pluginUploadOption.RemoteJenkins != "" { + if jenkins := findJenkinsByName(pluginUploadOption.RemoteJenkins); jenkins != nil { + pluginUploadOption.RemoteUser = jenkins.UserName + pluginUploadOption.RemotePassword = jenkins.Token + } + } + + pluginUploadOption.pluginFilePath = fmt.Sprintf("%s.hpi", file.Name()) + downloader := util.HTTPDownloader{ + TargetFilePath: pluginUploadOption.pluginFilePath, + URL: pluginUploadOption.Remote, + UserName: pluginUploadOption.RemoteUser, + Password: pluginUploadOption.RemotePassword, + ShowProgress: true, + Debug: rootOptions.Debug, + } + + if err := downloader.DownloadFile(); err != nil { + log.Fatal(err) + } + } else if len(args) == 0 { + path, _ := os.Getwd() + dirName := filepath.Base(path) + dirName = strings.Replace(dirName, "-plugin", "", -1) + path += fmt.Sprintf("/target/%s.hpi", dirName) + + pluginUploadOption.pluginFilePath = path + } else { + pluginUploadOption.pluginFilePath = args[0] + } + }, Run: func(cmd *cobra.Command, args []string) { jenkins := getCurrentJenkins() jclient := &client.PluginManager{} @@ -21,7 +88,12 @@ var pluginUploadCmd = &cobra.Command{ jclient.Token = jenkins.Token jclient.Proxy = jenkins.Proxy jclient.ProxyAuth = jenkins.ProxyAuth + jclient.Debug = rootOptions.Debug + + if pluginUploadOption.Remote != "" { + defer os.Remove(pluginUploadOption.pluginFilePath) + } - jclient.Upload() + jclient.Upload(pluginUploadOption.pluginFilePath) }, } diff --git a/app/cmd/root.go b/app/cmd/root.go index 6cb83e978e7f54b002793c21abc9a04e166eef30..59e4939e3cafea08d895e6a34e27625574ed5413 100644 --- a/app/cmd/root.go +++ b/app/cmd/root.go @@ -11,6 +11,7 @@ import ( type RootOptions struct { Version bool + Debug bool } var rootCmd = &cobra.Command{ @@ -48,6 +49,7 @@ var rootOptions RootOptions func init() { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().BoolVarP(&rootOptions.Version, "version", "v", false, "Print the version of Jenkins CLI") + rootCmd.PersistentFlags().BoolVarP(&rootOptions.Debug, "debug", "", false, "Print the output into debug.html") } func initConfig() { diff --git a/client/common.go b/client/common.go index ae6f2a106041831d65755adbaee2913b2857c60a..c1f51b95e23fe1a5d93791d7071ac6c9c379dbc4 100644 --- a/client/common.go +++ b/client/common.go @@ -18,6 +18,8 @@ type JenkinsCore struct { Token string Proxy string ProxyAuth string + + Debug bool } type JenkinsCrumb struct { diff --git a/client/pluginManger.go b/client/pluginManger.go index a04a09f218ad13a368b08a20989fa6829e6aa1cb..b0c20aad71fe3e42f726307c67fcc94e12192663 100644 --- a/client/pluginManger.go +++ b/client/pluginManger.go @@ -13,7 +13,7 @@ import ( "path/filepath" "strings" - "github.com/gosuri/uiprogress" + "github.com/linuxsuren/jenkins-cli/util" ) type PluginManager struct { @@ -220,15 +220,11 @@ func (p *PluginManager) UninstallPlugin(name string) (err error) { return } -func (p *PluginManager) Upload() { +// Upload will upload a file from local filesystem into Jenkins +func (p *PluginManager) Upload(pluginFile string) { api := fmt.Sprintf("%s/pluginManager/uploadPlugin", p.URL) - - path, _ := os.Getwd() - dirName := filepath.Base(path) - dirName = strings.Replace(dirName, "-plugin", "", -1) - path += fmt.Sprintf("/target/%s.hpi", dirName) extraParams := map[string]string{} - request, err := newfileUploadRequest(api, extraParams, "@name", path) + request, err := newfileUploadRequest(api, extraParams, "@name", pluginFile) if err != nil { log.Fatal(err) } @@ -245,9 +241,10 @@ func (p *PluginManager) Upload() { if err != nil { log.Fatal(err) } else if response.StatusCode != 200 { + fmt.Println("StatusCode", response.StatusCode) var data []byte - if data, err = ioutil.ReadAll(response.Body); err == nil { - fmt.Println(string(data)) + if data, err = ioutil.ReadAll(response.Body); err == nil && p.Debug { + ioutil.WriteFile("debug.html", data, 0664) } else { log.Fatal(err) } @@ -261,34 +258,6 @@ func (p *PluginManager) handleCheck(handle func(*http.Response)) func(*http.Resp return handle } -type ProgressIndicator struct { - bytes.Buffer - Total float64 - count float64 - bar *uiprogress.Bar -} - -func (i *ProgressIndicator) Init() { - uiprogress.Start() // start rendering - i.bar = uiprogress.AddBar(100) // Add a new bar - - // optionally, append and prepend completion and elapsed time - i.bar.AppendCompleted() - // i.bar.PrependElapsed() -} - -func (i *ProgressIndicator) Write(p []byte) (n int, err error) { - n, err = i.Buffer.Write(p) - return -} - -func (i *ProgressIndicator) Read(p []byte) (n int, err error) { - n, err = i.Buffer.Read(p) - i.count += float64(n) - i.bar.Set((int)(i.count * 100 / i.Total)) - return -} - func newfileUploadRequest(uri string, params map[string]string, paramName, path string) (*http.Request, error) { file, err := os.Open(path) if err != nil { @@ -303,12 +272,15 @@ func newfileUploadRequest(uri string, params map[string]string, paramName, path } defer file.Close() - // body := &bytes.Buffer{} - body := &ProgressIndicator{ - Total: total, + bytesBuffer := &bytes.Buffer{} + progressWriter := &util.ProgressIndicator{ + Total: total, + Writer: bytesBuffer, + Reader: bytesBuffer, + Title: "Uploading", } - body.Init() - writer := multipart.NewWriter(body) + progressWriter.Init() + writer := multipart.NewWriter(bytesBuffer) part, err := writer.CreateFormFile(paramName, filepath.Base(path)) if err != nil { return nil, err @@ -324,7 +296,7 @@ func newfileUploadRequest(uri string, params map[string]string, paramName, path return nil, err } - req, err := http.NewRequest("POST", uri, body) + req, err := http.NewRequest("POST", uri, progressWriter) req.Header.Set("Content-Type", writer.FormDataContentType()) return req, err } diff --git a/util/http.go b/util/http.go new file mode 100644 index 0000000000000000000000000000000000000000..f301e5cdc9feaf77d197bcb84702d6ed034affb9 --- /dev/null +++ b/util/http.go @@ -0,0 +1,126 @@ +package util + +import ( + "crypto/tls" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "strconv" + + "github.com/gosuri/uiprogress" +) + +type HTTPDownloader struct { + TargetFilePath string + URL string + ShowProgress bool + + UserName string + Password string + + Debug bool +} + +// DownloadFile download a file with the progress +func (h *HTTPDownloader) DownloadFile() error { + filepath, url, showProgress := h.TargetFilePath, h.URL, h.ShowProgress + // Get the data + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + + if h.UserName != "" && h.Password != "" { + req.SetBasicAuth(h.UserName, h.Password) + } + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + if h.Debug { + if data, err := ioutil.ReadAll(resp.Body); err == nil { + ioutil.WriteFile("debug-download.html", data, 0664) + } + } + return fmt.Errorf("Invalidate status code: %d", resp.StatusCode) + } + + writer := &ProgressIndicator{ + Title: "Downloading", + } + if showProgress { + if total, ok := resp.Header["Content-Length"]; ok && len(total) > 0 { + fileLength, err := strconv.ParseInt(total[0], 10, 64) + if err == nil { + writer.Total = float64(fileLength) + } + } + } + // Create the file + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + writer.Writer = out + writer.Init() + + // Write the body to file + _, err = io.Copy(writer, resp.Body) + return err +} + +// ProgressIndicator hold the progress of io operation +type ProgressIndicator struct { + Writer io.Writer + Reader io.Reader + Title string + + // bytes.Buffer + Total float64 + count float64 + bar *uiprogress.Bar +} + +// Init set the default value for progress indicator +func (i *ProgressIndicator) Init() { + uiprogress.Start() // start rendering + i.bar = uiprogress.AddBar(100) // Add a new bar + + // optionally, append and prepend completion and elapsed time + i.bar.AppendCompleted() + // i.bar.PrependElapsed() + + if i.Title != "" { + i.bar.PrependFunc(func(b *uiprogress.Bar) string { + return fmt.Sprintf("%s: ", i.Title) + }) + } +} + +func (i *ProgressIndicator) Write(p []byte) (n int, err error) { + n, err = i.Writer.Write(p) + i.setBar(n) + return +} + +func (i *ProgressIndicator) Read(p []byte) (n int, err error) { + n, err = i.Reader.Read(p) + i.setBar(n) + return +} + +func (i *ProgressIndicator) setBar(n int) { + i.count += float64(n) + i.bar.Set((int)(i.count * 100 / i.Total)) +} diff --git a/util/table.go b/util/table.go index 18d3b162688d7be6d7af2b300b4ac0f41141d2a4..1f6c97fee7b5fcce56aecc68d13f392dc72825a3 100644 --- a/util/table.go +++ b/util/table.go @@ -6,12 +6,6 @@ import ( "unicode/utf8" ) -// const ( -// ALIGN_LEFT = 0 -// ALIGN_CENTER = 1 -// ALIGN_RIGHT = 2 -// ) - type Table struct { Out io.Writer Rows [][]string