Skip to content

Commit ad9ecd3

Browse files
committed
Inital import project
0 parents  commit ad9ecd3

File tree

16 files changed

+1482
-0
lines changed

16 files changed

+1482
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 Zhao Xiaojie
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build:
2+
go build ./pkg

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[![](https://goreportcard.com/badge/linuxsuren/go-cli-plugin)](https://goreportcard.com/report/linuxsuren/go-cli-plugin)
2+
[![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](https://godoc.org/github.com/linuxsuren/go-cli-plugin)
3+
[![Contributors](https://img.shields.io/github/contributors/linuxsuren/go-cli-plugin.svg)](https://github.com/linuxsuren/go-cli-plugin/graphs/contributors)
4+
[![GitHub release](https://img.shields.io/github/release/linuxsuren/go-cli-plugin.svg?label=release)](https://github.com/linuxsuren/go-cli-plugin/releases/latest)
5+
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/linuxsuren/go-cli-plugin)
6+
[![HitCount](http://hits.dwyl.com/linuxsuren/go-cli-plugin.svg)](http://hits.dwyl.com/linuxsuren/go-cli-plugin)
7+
8+
This project aims to provide an easy way to let you writing a plugin for your CLI project.
9+
10+
# Original
11+
12+
This project originally comes from [jcli](https://github.com/linuxsuren/go-cli-plugin).

go.mod

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module github.com/linuxsuren/go-cli-plugin
2+
3+
go 1.15
4+
5+
require (
6+
github.com/gosuri/uiprogress v0.0.1
7+
github.com/jenkins-zh/jenkins-cli v0.0.32
8+
github.com/mitchellh/go-homedir v1.1.0
9+
github.com/pkg/errors v0.9.1
10+
github.com/spf13/cobra v1.1.1
11+
go.uber.org/zap v1.16.0
12+
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392
13+
gopkg.in/src-d/go-git.v4 v4.13.1
14+
gopkg.in/yaml.v2 v2.4.0
15+
)

go.sum

Lines changed: 483 additions & 0 deletions
Large diffs are not rendered by default.

pkg/cmd/completion.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package config
2+
3+
import (
4+
"github.com/linuxsuren/go-cli-plugin/pkg"
5+
"github.com/spf13/cobra"
6+
"strings"
7+
)
8+
9+
// ValidPluginNames returns the valid plugin name list
10+
func ValidPluginNames(cmd *cobra.Command, args []string, prefix string) (pluginNames []string, directive cobra.ShellCompDirective) {
11+
directive = cobra.ShellCompDirectiveNoFileComp
12+
if plugins, err := pkg.FindPlugins(); err == nil {
13+
pluginNames = make([]string, 0)
14+
for i := range plugins {
15+
plugin := plugins[i]
16+
name := plugin.Use
17+
18+
switch cmd.Use {
19+
case "install":
20+
if plugin.Installed {
21+
continue
22+
}
23+
case "uninstall":
24+
if !plugin.Installed {
25+
continue
26+
}
27+
}
28+
29+
duplicated := false
30+
for j := range args {
31+
if name == args[j] {
32+
duplicated = true
33+
break
34+
}
35+
}
36+
37+
if !duplicated && strings.HasPrefix(name, prefix) {
38+
pluginNames = append(pluginNames, name)
39+
}
40+
}
41+
}
42+
return
43+
}
44+
45+
// NoFileCompletion avoid completion with files
46+
func NoFileCompletion(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
47+
return nil, cobra.ShellCompDirectiveNoFileComp
48+
}

pkg/cmd/fetch.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"github.com/mitchellh/go-homedir"
6+
"github.com/spf13/cobra"
7+
"golang.org/x/crypto/ssh"
8+
"gopkg.in/src-d/go-git.v4"
9+
"gopkg.in/src-d/go-git.v4/plumbing/transport"
10+
githttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
11+
gitssh "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh"
12+
"io/ioutil"
13+
"os"
14+
"strings"
15+
)
16+
17+
// NewConfigPluginFetchCmd create a command for fetching plugin metadata
18+
func NewConfigPluginFetchCmd(pluginOrg, pluginRepo string) (cmd *cobra.Command) {
19+
pluginFetchCmd := jcliPluginFetchCmd{
20+
PluginOrg: pluginOrg,
21+
PluginRepo: pluginRepo,
22+
}
23+
24+
cmd = &cobra.Command{
25+
Use: "fetch",
26+
Short: "fetch metadata of plugins",
27+
Long: fmt.Sprintf(`fetch metadata of plugins
28+
The official metadata git repository is https://github.com/%s/%s,
29+
but you can change it by giving a command parameter.`, pluginFetchCmd.PluginOrg, pluginFetchCmd.PluginRepo),
30+
ValidArgsFunction: NoFileCompletion,
31+
RunE: pluginFetchCmd.Run,
32+
}
33+
34+
// add flags
35+
flags := cmd.Flags()
36+
flags.StringVarP(&pluginFetchCmd.PluginRepo, "plugin-repo", "",
37+
fmt.Sprintf("https://github.com/%s/%s", pluginFetchCmd.PluginOrg, pluginFetchCmd.PluginRepo),
38+
"The plugin git repository URL")
39+
flags.BoolVarP(&pluginFetchCmd.Reset, "reset", "", true,
40+
"If you want to reset the git local repo when pulling it")
41+
flags.StringVarP(&pluginFetchCmd.Username, "username", "u", "",
42+
"The username of git repository")
43+
flags.StringVarP(&pluginFetchCmd.Password, "password", "p", "",
44+
"The password of git repository")
45+
46+
sshKeyFile := fmt.Sprintf("%s/.ssh/id_rsa", os.Getenv("HOME"))
47+
flags.StringVarP(&pluginFetchCmd.SSHKeyFile, "ssh-key-file", "", sshKeyFile,
48+
"SSH key file")
49+
return
50+
}
51+
52+
// Run is the main entry point of plugin fetch command
53+
func (c *jcliPluginFetchCmd) Run(cmd *cobra.Command, args []string) (err error) {
54+
var userHome string
55+
if userHome, err = homedir.Dir(); err != nil {
56+
return
57+
}
58+
59+
pluginRepo := fmt.Sprintf("%s/.jenkins-cli/plugins-repo", userHome)
60+
c.output = cmd.OutOrStdout()
61+
62+
var r *git.Repository
63+
if r, err = git.PlainOpen(pluginRepo); err == nil {
64+
var w *git.Worktree
65+
if w, err = r.Worktree(); err != nil {
66+
return
67+
}
68+
69+
if c.Reset {
70+
if err = w.Reset(&git.ResetOptions{
71+
Mode: git.HardReset,
72+
}); err != nil {
73+
return
74+
}
75+
}
76+
77+
err = w.Pull(c.getPullOptions())
78+
if err == git.NoErrAlreadyUpToDate {
79+
err = nil // consider it's ok
80+
}
81+
} else {
82+
cloneOptions := c.getCloneOptions()
83+
_, err = git.PlainClone(pluginRepo, false, cloneOptions)
84+
}
85+
return
86+
}
87+
88+
func (c *jcliPluginFetchCmd) getCloneOptions() (cloneOptions *git.CloneOptions) {
89+
cloneOptions = &git.CloneOptions{
90+
URL: c.PluginRepo,
91+
Progress: c.output,
92+
Auth: c.getAuth(),
93+
}
94+
return
95+
}
96+
97+
func (c *jcliPluginFetchCmd) getPullOptions() (pullOptions *git.PullOptions) {
98+
pullOptions = &git.PullOptions{
99+
RemoteName: "origin",
100+
Progress: c.output,
101+
Auth: c.getAuth(),
102+
}
103+
return
104+
}
105+
106+
func (c *jcliPluginFetchCmd) getAuth() (auth transport.AuthMethod) {
107+
if c.Username != "" {
108+
auth = &githttp.BasicAuth{
109+
Username: c.Username,
110+
Password: c.Password,
111+
}
112+
}
113+
114+
if strings.HasPrefix(c.PluginRepo, "git@") {
115+
if sshKey, err := ioutil.ReadFile(c.SSHKeyFile); err == nil {
116+
signer, _ := ssh.ParsePrivateKey(sshKey)
117+
auth = &gitssh.PublicKeys{User: "git", Signer: signer}
118+
}
119+
}
120+
return
121+
}

pkg/cmd/install.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package config
2+
3+
import (
4+
"archive/tar"
5+
"compress/gzip"
6+
"fmt"
7+
"github.com/linuxsuren/go-cli-plugin/pkg"
8+
"github.com/mitchellh/go-homedir"
9+
"github.com/spf13/cobra"
10+
"gopkg.in/yaml.v2"
11+
"io"
12+
"io/ioutil"
13+
"os"
14+
"path/filepath"
15+
"runtime"
16+
)
17+
18+
// NewConfigPluginInstallCmd create a command for fetching plugin metadata
19+
func NewConfigPluginInstallCmd(pluginOrg, pluginRepo string) (cmd *cobra.Command) {
20+
pluginInstallCmd := jcliPluginInstallCmd{
21+
PluginOrg: pluginOrg,
22+
PluginRepo: pluginRepo,
23+
}
24+
25+
cmd = &cobra.Command{
26+
Use: "install",
27+
Short: "install a jcli plugin",
28+
Long: "install a jcli plugin",
29+
Args: cobra.MinimumNArgs(1),
30+
ValidArgsFunction: ValidPluginNames,
31+
RunE: pluginInstallCmd.Run,
32+
}
33+
34+
// add flags
35+
flags := cmd.Flags()
36+
flags.BoolVarP(&pluginInstallCmd.ShowProgress, "show-progress", "", true,
37+
"If you want to show the progress of download")
38+
return
39+
}
40+
41+
// Run main entry point for plugin install command
42+
func (c *jcliPluginInstallCmd) Run(cmd *cobra.Command, args []string) (err error) {
43+
name := args[0]
44+
var userHome string
45+
if userHome, err = homedir.Dir(); err != nil {
46+
return
47+
}
48+
49+
var data []byte
50+
pluginsMetadataFile := fmt.Sprintf("%s/.jenkins-cli/plugins-repo/%s.yaml", userHome, name)
51+
if data, err = ioutil.ReadFile(pluginsMetadataFile); err == nil {
52+
plugin := pkg.Plugin{}
53+
if err = yaml.Unmarshal(data, &plugin); err == nil {
54+
err = c.download(plugin)
55+
}
56+
}
57+
58+
if err == nil {
59+
cachedMetadataFile := pkg.GetJCLIPluginPath(userHome, name, true)
60+
err = ioutil.WriteFile(cachedMetadataFile, data, 0664)
61+
}
62+
return
63+
}
64+
65+
func (c *jcliPluginInstallCmd) download(plu pkg.Plugin) (err error) {
66+
var userHome string
67+
if userHome, err = homedir.Dir(); err != nil {
68+
return
69+
}
70+
71+
link := c.getDownloadLink(plu)
72+
output := fmt.Sprintf("%s/.jenkins-cli/plugins/%s.tar.gz", userHome, plu.Main)
73+
74+
downloader := pkg.HTTPDownloader{
75+
RoundTripper: c.RoundTripper,
76+
TargetFilePath: output,
77+
URL: link,
78+
ShowProgress: c.ShowProgress,
79+
}
80+
if err = downloader.DownloadFile(); err == nil {
81+
err = c.extractFiles(plu, output)
82+
}
83+
return
84+
}
85+
86+
func (c *jcliPluginInstallCmd) getDownloadLink(plu pkg.Plugin) (link string) {
87+
link = plu.DownloadLink
88+
if link == "" {
89+
operationSystem := runtime.GOOS
90+
arch := runtime.GOARCH
91+
link = fmt.Sprintf("https://github.com/%s/%s/releases/download/%s/%s-%s-%s.tar.gz",
92+
c.PluginOrg, plu.Main, plu.Version, plu.Main, operationSystem, arch)
93+
}
94+
return
95+
}
96+
97+
func (c *jcliPluginInstallCmd) extractFiles(plugin pkg.Plugin, tarFile string) (err error) {
98+
var f *os.File
99+
var gzf *gzip.Reader
100+
if f, err = os.Open(tarFile); err != nil {
101+
return
102+
}
103+
defer f.Close()
104+
105+
if gzf, err = gzip.NewReader(f); err != nil {
106+
return
107+
}
108+
109+
tarReader := tar.NewReader(gzf)
110+
var header *tar.Header
111+
for {
112+
if header, err = tarReader.Next(); err == io.EOF {
113+
err = nil
114+
break
115+
} else if err != nil {
116+
break
117+
}
118+
name := header.Name
119+
120+
switch header.Typeflag {
121+
case tar.TypeReg:
122+
if name != plugin.Main {
123+
continue
124+
}
125+
var targetFile *os.File
126+
if targetFile, err = os.OpenFile(fmt.Sprintf("%s/%s", filepath.Dir(tarFile), name),
127+
os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)); err != nil {
128+
break
129+
}
130+
if _, err = io.Copy(targetFile, tarReader); err != nil {
131+
break
132+
}
133+
targetFile.Close()
134+
}
135+
}
136+
return
137+
}

pkg/cmd/list.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package config
2+
3+
import (
4+
"github.com/linuxsuren/go-cli-plugin/pkg"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
// NewConfigPluginListCmd create a command for list all jcli plugins
9+
func NewConfigPluginListCmd() (cmd *cobra.Command) {
10+
configPluginListCmd := configPluginListCmd{}
11+
12+
cmd = &cobra.Command{
13+
Use: "list",
14+
Short: "List all installed plugins",
15+
Long: "List all installed plugins",
16+
RunE: configPluginListCmd.RunE,
17+
ValidArgsFunction: NoFileCompletion,
18+
}
19+
20+
configPluginListCmd.SetFlagWithHeaders(cmd, "Use,Version,Installed,DownloadLink")
21+
return
22+
}
23+
24+
// RunE is the main entry point of config plugin list command
25+
func (c *configPluginListCmd) RunE(cmd *cobra.Command, args []string) (err error) {
26+
c.Writer = cmd.OutOrStdout()
27+
var plugins []pkg.Plugin
28+
if plugins, err = pkg.FindPlugins(); err == nil {
29+
err = c.OutputV2(plugins)
30+
}
31+
return
32+
}

0 commit comments

Comments
 (0)