From 99bdef3bc14398976db25d97e67d25f896d0c68a Mon Sep 17 00:00:00 2001 From: Yanjun Shi Date: Sun, 2 Feb 2020 13:19:37 +0800 Subject: [PATCH] Add support upgrade all plugins (#258) * Add all and compatible for upgrade plugins * Add comment * Add some unit test * update unit test * updated unified call http request * Update unit test coverage * Update unit test coverage * Temporarily comment out the compatibility section * comment the other test unit * restore some comment * delete files that do not exist * delete comment content for completion method * Update pluginApi_test_common.go Co-authored-by: Zhao Xiaojie --- app/cmd/plugin_upgrade.go | 59 +++++++++++++++++++++++++++--- app/cmd/plugin_upgrade_test.go | 21 +++++++++++ client/pluginApi.go | 63 ++++++++++++++++++++++++++++++--- client/pluginApi_test.go | 9 +++++ client/pluginApi_test_common.go | 21 ++++++++++- 5 files changed, 163 insertions(+), 10 deletions(-) diff --git a/app/cmd/plugin_upgrade.go b/app/cmd/plugin_upgrade.go index fe61000..1f1fc6f 100644 --- a/app/cmd/plugin_upgrade.go +++ b/app/cmd/plugin_upgrade.go @@ -17,6 +17,7 @@ import ( // PluginUpgradeOption option for plugin list command type PluginUpgradeOption struct { Filter []string + All bool RoundTripper http.RoundTripper } @@ -25,7 +26,9 @@ var pluginUpgradeOption PluginUpgradeOption func init() { pluginCmd.AddCommand(pluginUpgradeCmd) - pluginUpgradeCmd.Flags().StringArrayVarP(&pluginUpgradeOption.Filter, "filter", "", []string{}, "Filter for the list, like: name=foo") + pluginUpgradeCmd.Flags().StringArrayVarP(&pluginUpgradeOption.Filter, "filter", "", []string{}, i18n.T("Filter for the list, like: name=foo")) + pluginUpgradeCmd.Flags().BoolVarP(&pluginUpgradeOption.All, "all", "", false, i18n.T("Upgrade all plugins for updated")) + } var pluginUpgradeCmd = &cobra.Command{ @@ -43,7 +46,11 @@ var pluginUpgradeCmd = &cobra.Command{ var err error targetPlugins := make([]string, 0) - if len(args) == 0 { + if pluginUpgradeOption.All { + if upgradeablePlugins, err := pluginUpgradeOption.findUpgradeablePlugins(jclient); err == nil { + targetPlugins = pluginUpgradeOption.convertToArray(upgradeablePlugins) + } + } else if len(args) == 0 { var upgradeablePlugins []client.InstalledPlugin if upgradeablePlugins, err = pluginUpgradeOption.findUpgradeablePlugins(jclient); err == nil { prompt := &survey.MultiSelect{ @@ -56,7 +63,7 @@ var pluginUpgradeCmd = &cobra.Command{ targetPlugins = args } - if err == nil { + if err == nil && len(targetPlugins) != 0 { err = jclient.InstallPlugin(targetPlugins) } helper.CheckErr(cmd, err) @@ -90,7 +97,6 @@ func (p *PluginUpgradeOption) findUpgradeablePlugins(jclient *client.PluginManag if plugins, err = jclient.GetPlugins(1); err != nil { return } - for _, plugin := range plugins.Plugins { if !plugin.HasUpdate { continue @@ -104,3 +110,48 @@ func (p *PluginUpgradeOption) findUpgradeablePlugins(jclient *client.PluginManag } return } + +/*func (p *PluginUpgradeOption) findCompatiblePlugins(installedPlugins []client.InstalledPlugin) (plugins []string) { + plugins = make([]string, 0) + var pluginNames string + for i, plugin := range installedPlugins { + if !strings.Contains(pluginNames, plugin.ShortName) { + if len(installedPlugins) > i+1 { + pluginNames += plugin.ShortName + "|" + } else { + pluginNames += plugin.ShortName + } + } + } + plugins = pluginUpgradeOption.assembleData(installedPlugins, pluginNames) + return +} + +func (p *PluginUpgradeOption) assembleData(installedPlugins []client.InstalledPlugin, pluginNames string) (plugins []string) { + pluginAPI := client.PluginAPI{} + if pluginsList, err := pluginAPI.BatchSearchPlugins(pluginNames); err == nil { + for _, pluginInfo := range pluginsList { + for _, plugin := range installedPlugins { + if plugin.ShortName == pluginInfo.Name { + var hasSecurity bool + securityWarnings := pluginInfo.SecurityWarnings + hasSecurity = p.checkSecurity(securityWarnings) + if !hasSecurity { + plugins = append(plugins, pluginInfo.Name) + } + } + } + } + } + return +} + +func (p *PluginUpgradeOption) checkSecurity(securityWarnings []client.SecurityWarning) (hasSecurity bool) { + for _, securityWarning := range securityWarnings { + if securityWarning.Active { + hasSecurity = true + break + } + } + return +}*/ diff --git a/app/cmd/plugin_upgrade_test.go b/app/cmd/plugin_upgrade_test.go index 0778823..5c9494a 100644 --- a/app/cmd/plugin_upgrade_test.go +++ b/app/cmd/plugin_upgrade_test.go @@ -84,5 +84,26 @@ var _ = Describe("plugin upgrade command", func() { Expect(len(plugins)).To(Equal(1)) Expect(plugins[0]).To(Equal(pluginName)) }) + + It("upgrade all plugin, should success", func() { + data, err := generateSampleConfig() + Expect(err).To(BeNil()) + err = ioutil.WriteFile(rootOptions.ConfigFile, data, 0664) + Expect(err).To(BeNil()) + + request, _ := client.PrepareForOneInstalledPlugin(roundTripper, "http://localhost:8080/jenkins") + request.SetBasicAuth("admin", "111e3a2f0231198855dceaff96f20540a9") + //client.PrepareShowPlugins(roundTripper, pluginName) + client.PrepareForInstallPlugin(roundTripper, "http://localhost:8080/jenkins", pluginName, "admin", "111e3a2f0231198855dceaff96f20540a9") + + rootCmd.SetArgs([]string{"plugin", "upgrade", "--all"}) + + buf := new(bytes.Buffer) + rootCmd.SetOutput(buf) + _, err = rootCmd.ExecuteC() + Expect(err).To(BeNil()) + + Expect(buf.String()).To(Equal("")) + }) }) }) diff --git a/client/pluginApi.go b/client/pluginApi.go index d67ca91..0bb3e87 100644 --- a/client/pluginApi.go +++ b/client/pluginApi.go @@ -51,8 +51,8 @@ type PluginInfo struct { Title string `json:"title"` URL string `json:"url"` Version string `json:"version"` - - Stats PluginInfoStats + SecurityWarnings []SecurityWarning `json:"securityWarnings"` + Stats PluginInfoStats } // PluginInfoStats is the plugin info stats @@ -73,10 +73,34 @@ type PluginInstallationInfo struct { Percentage float64 } +// SecurityWarning represents the plugin security-warining info +type SecurityWarning struct { + Active bool + ID string + Message string + URL string + Versions []Version +} + +// Version represents the SecurityWarning cover version +type Version struct { + firstVersion string + lastVersion string +} + +// Plugins represents multi PluginInfo +type Plugins struct { + Limit int `json:"limit"` + Page int `json:"page"` + Pages int `json:"pages"` + Total int `json:"total"` + Plugins []PluginInfo `json:"plugins"` +} + // ShowTrend show the trend of plugins func (d *PluginAPI) ShowTrend(name string) (trend string, err error) { var plugin *PluginInfo - if plugin, err = d.getPlugin(name); err != nil { + if plugin, err = d.GetPlugin(name); err != nil { return } @@ -141,7 +165,8 @@ func (d *PluginAPI) download(url string, name string) (err error) { return } -func (d *PluginAPI) getPlugin(name string) (plugin *PluginInfo, err error) { +// GetPlugin will get the plugin information +func (d *PluginAPI) GetPlugin(name string) (plugin *PluginInfo, err error) { var cli = http.Client{} if d.RoundTripper == nil { cli.Transport = &http.Transport{ @@ -168,7 +193,7 @@ func (d *PluginAPI) getPlugin(name string) (plugin *PluginInfo, err error) { } func (d *PluginAPI) collectDependencies(pluginName string) (plugins []PluginInfo) { - plugin, err := d.getPlugin(pluginName) + plugin, err := d.GetPlugin(pluginName) if err != nil { log.Println("can't get the plugin by name:", pluginName) panic(err) @@ -192,3 +217,31 @@ func (d *PluginAPI) collectDependencies(pluginName string) (plugins []PluginInfo } return } + +// BatchSearchPlugins will batch search plugins +func (d *PluginAPI) BatchSearchPlugins(pluginNames string) (plugins []PluginInfo, err error) { + var cli = http.Client{} + if d.RoundTripper == nil { + cli.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + } else { + cli.Transport = d.RoundTripper + } + + pluginAPI := fmt.Sprintf("https://plugins.jenkins.io/api/plugins/?q=%s&page=1&limit=1000", pluginNames) + logger.Debug("fetch data from plugin API", zap.String("url", pluginAPI)) + + var resp *http.Response + if resp, err = cli.Get(pluginAPI); err == nil { + var body []byte + if body, err = ioutil.ReadAll(resp.Body); err == nil { + var pluginsInfo Plugins + err = json.Unmarshal(body, &pluginsInfo) + plugins = pluginsInfo.Plugins + } + } + return +} diff --git a/client/pluginApi_test.go b/client/pluginApi_test.go index d1cd195..06dd04d 100644 --- a/client/pluginApi_test.go +++ b/client/pluginApi_test.go @@ -133,5 +133,14 @@ var _ = Describe("plugin api test", func() { defer os.Remove("fake.hpi") }) + + It("batch search plugins", func() { + names = []string{"fake"} + + PrepareShowPlugins(roundTripper, "fake") + + plugins, _ := pluginAPI.BatchSearchPlugins("fake") + Expect(len(plugins)).To(Equal(0)) + }) }) }) diff --git a/client/pluginApi_test_common.go b/client/pluginApi_test_common.go index 33e5b51..0ca0f16 100644 --- a/client/pluginApi_test_common.go +++ b/client/pluginApi_test_common.go @@ -15,7 +15,6 @@ func PrepareShowTrend(roundTripper *mhttp.MockRoundTripper, keyword string) ( request, _ := http.NewRequest("GET", fmt.Sprintf("https://plugins.jenkins.io/api/plugin/%s", keyword), nil) response = &http.Response{ StatusCode: 200, - Proto: "HTTP/1.1", Request: request, Body: ioutil.NopCloser(bytes.NewBufferString(` {"name":"fake","version": "0.1.8","url": "http://updates.jenkins-ci.org/download/plugins/hugo/0.1.8/hugo.hpi", @@ -75,3 +74,23 @@ func PrepareCheckUpdate(roundTripper *mhttp.MockRoundTripper, rootURL, user, pas request, _ := http.NewRequest("POST", api, nil) PrepareCommonPost(request, "", roundTripper, user, password, rootURL) } + +// PrepareShowPlugins only for test +func PrepareShowPlugins(roundTripper *mhttp.MockRoundTripper, keyword string) ( + response *http.Response) { + request, _ := http.NewRequest("GET", fmt.Sprintf("https://plugins.jenkins.io/api/plugins/?q=%s&page=1&limit=1000", keyword), nil) + response = &http.Response{ + StatusCode: 200, + Request: request, + Body: ioutil.NopCloser(bytes.NewBufferString(` + "limit":1000,"page":1,"pages"":1,"total":1, + "plugins":[{"name":"fake","version": "0.1.8","url": "http://updates.jenkins-ci.org/download/plugins/hugo/0.1.8/hugo.hpi", + "stats": {"installations":[{"total":1512},{"total":3472},{"total":4385},{"total":3981}]}, + "securityWarnings":[{"versions":[{"firstVersion":null,"lastVersion":"0.1.8"}],"id":"SECURITY-659", + "message":"XML External Entity (XXE) processing vulnerability","url":"https://jenkins.io/security/advisory/2018-02-05/","active":true}]}] + `)), + } + roundTripper.EXPECT(). + RoundTrip(request).Return(response, nil) + return +} -- GitLab