From 24cee7dca2b7ef74f5c7f99bef140ce044ae292b Mon Sep 17 00:00:00 2001 From: Zhao Xiaojie Date: Tue, 26 Nov 2019 20:31:21 +0800 Subject: [PATCH] Add sub shell support (#253) * Add sub shell support * Remove logger print --- app/cmd/config.go | 7 +- app/cmd/config_select.go | 5 +- app/cmd/job_type_test.go | 5 +- app/cmd/root.go | 56 ++++++----- app/cmd/shell.go | 125 ++++++++++++++++++++++++ app/i18n/jcli/zh_CN/LC_MESSAGES/jcli.po | 8 +- go.mod | 3 + go.sum | 12 ++- 8 files changed, 186 insertions(+), 35 deletions(-) create mode 100644 app/cmd/shell.go diff --git a/app/cmd/config.go b/app/cmd/config.go index a027681..b7058b0 100644 --- a/app/cmd/config.go +++ b/app/cmd/config.go @@ -203,8 +203,13 @@ func saveConfig() (err error) { var data []byte config := getConfig() + configPath := configOptions.ConfigFileLocation + if rootOptions.ConfigFile != "" { + configPath = rootOptions.ConfigFile + } + if data, err = yaml.Marshal(&config); err == nil { - err = ioutil.WriteFile(configOptions.ConfigFileLocation, data, 0644) + err = ioutil.WriteFile(configPath, data, 0644) } return } diff --git a/app/cmd/config_select.go b/app/cmd/config_select.go index d21c9e9..f2f3dfe 100644 --- a/app/cmd/config_select.go +++ b/app/cmd/config_select.go @@ -2,6 +2,7 @@ package cmd import ( "github.com/AlecAivazis/survey/v2" + "github.com/jenkins-zh/jenkins-cli/app/i18n" "github.com/spf13/cobra" ) @@ -11,8 +12,8 @@ func init() { var configSelectCmd = &cobra.Command{ Use: "select []", - Short: "Select one config as current Jenkins", - Long: `Select one config as current Jenkins`, + Short: i18n.T("Select one config as current Jenkins"), + Long: i18n.T("Select one config as current Jenkins"), Run: func(_ *cobra.Command, args []string) { if len(args) > 0 { jenkinsName := args[0] diff --git a/app/cmd/job_type_test.go b/app/cmd/job_type_test.go index 0e12b82..94a049d 100644 --- a/app/cmd/job_type_test.go +++ b/app/cmd/job_type_test.go @@ -2,11 +2,12 @@ package cmd import ( "bytes" - "github.com/jenkins-zh/jenkins-cli/client" "io/ioutil" "net/http" "os" + "github.com/jenkins-zh/jenkins-cli/client" + "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -54,7 +55,7 @@ var _ = Describe("job type command", func() { roundTripper.EXPECT(). RoundTrip(request).Return(response, nil) - initConfig() + //initConfig() jclient := &client.JobClient{ JenkinsCore: client.JenkinsCore{ RoundTripper: jobTypeOption.RoundTripper, diff --git a/app/cmd/root.go b/app/cmd/root.go index e61184d..dd3ea8e 100644 --- a/app/cmd/root.go +++ b/app/cmd/root.go @@ -34,11 +34,38 @@ var rootCmd = &cobra.Command{ Use: "jcli", Short: i18n.T("jcli is a tool which could help you with your multiple Jenkins"), Long: `jcli is Jenkins CLI which could help with your multiple Jenkins, - Manage your Jenkins and your pipelines - More information could found at https://jenkins-zh.cn`, +Manage your Jenkins and your pipelines +More information could found at https://jenkins-zh.cn`, PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { if logger, err = util.InitLogger(rootOptions.LoggerLevel); err == nil { client.SetLogger(logger) + } else { + return + } + + if rootOptions.ConfigFile == "" { + rootOptions.ConfigFile = os.Getenv("JCLI_CONFIG") + } + + logger.Debug("read config file", zap.String("path", rootOptions.ConfigFile)) + if rootOptions.Version && cmd.Flags().NFlag() == 1 { + return + } + + if rootOptions.ConfigFile == "" { + if err := loadDefaultConfig(); err != nil { + configLoadErrorHandle(err) + } + } else { + if err := loadConfig(rootOptions.ConfigFile); err != nil { + configLoadErrorHandle(err) + } + } + + // set Header Accept-Language + config = getConfig() + if config != nil { + client.SetLanguage(config.Language) } return }, @@ -60,7 +87,7 @@ var rootCmd = &cobra.Command{ }, } -// Execute will exectue the command +// Execute will execute the command func Execute() { if err := rootCmd.Execute(); err != nil { os.Exit(1) @@ -70,7 +97,6 @@ func Execute() { var rootOptions RootOptions func init() { - cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVarP(&rootOptions.ConfigFile, "configFile", "", "", i18n.T("An alternative config file")) rootCmd.PersistentFlags().StringVarP(&rootOptions.Jenkins, "jenkins", "j", "", @@ -83,26 +109,6 @@ func init() { rootCmd.SetOut(os.Stdout) } -func initConfig() { - if rootOptions.Version && rootCmd.Flags().NFlag() == 1 { - return - } - if rootOptions.ConfigFile == "" { - if err := loadDefaultConfig(); err != nil { - configLoadErrorHandle(err) - } - } else { - if err := loadConfig(rootOptions.ConfigFile); err != nil { - configLoadErrorHandle(err) - } - } - // set Header Accept-Language - config = getConfig() - if config != nil { - client.SetLanguage(config.Language) - } -} - func configLoadErrorHandle(err error) { if os.IsNotExist(err) { log.Printf("No config file found.") @@ -250,7 +256,7 @@ __jcli_custom_func() { __jcli_get_plugin_name return ;; - jcli_open | jcli_config_select | jcli_config_remove) + jcli_open | jcli_config_select | jcli_config_remove | jcli_shell) __jcli_get_config_name return ;; diff --git a/app/cmd/shell.go b/app/cmd/shell.go new file mode 100644 index 0000000..8a2cbdb --- /dev/null +++ b/app/cmd/shell.go @@ -0,0 +1,125 @@ +package cmd + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + + "go.uber.org/zap" + + "gopkg.in/yaml.v2" + + "github.com/jenkins-zh/jenkins-cli/app/i18n" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(shellCmd) +} + +const ( + defaultRcFile = ` +if [ -f /etc/bashrc ]; then + source /etc/bashrc +fi +if [ -f ~/.bashrc ]; then + source ~/.bashrc +fi +if type -t __start_jcli >/dev/null; then true; else + source <(jcli completion) +fi +` + + zshRcFile = ` +if [ -f /etc/zshrc ]; then + source /etc/zshrc +fi +if [ -f ~/.zshrc ]; then + source ~/.zshrc +fi +` +) + +var shellCmd = &cobra.Command{ + Use: "shell []", + Short: i18n.T("Create a sub shell so that changes to a specific Jenkins remain local to the shell."), + Long: i18n.T("Create a sub shell so that changes to a specific Jenkins remain local to the shell."), + Aliases: []string{"sh"}, + PreRun: func(cmd *cobra.Command, args []string) { + if len(args) > 0 { + jenkinsName := args[0] + setCurrentJenkins(jenkinsName) + } + }, + RunE: func(cmd *cobra.Command, _ []string) error { + tmpDirName, err := ioutil.TempDir("", ".jcli-shell-") + if err != nil { + return err + } + tmpConfigFileName := filepath.Join(tmpDirName, "/config") + + var data []byte + config := getConfig() + if data, err = yaml.Marshal(&config); err == nil { + err = ioutil.WriteFile(tmpConfigFileName, data, 0644) + } else { + return err + } + + fullShell := os.Getenv("SHELL") + shell := filepath.Base(fullShell) + if fullShell == "" && runtime.GOOS == "windows" { + // SHELL is set by git-bash but not cygwin :-( + shell = "cmd.exe" + } + + prompt := createNewBashPrompt(os.Getenv("PS1")) + rcFile := defaultRcFile + "\nexport PS1=" + prompt + "\nexport JCLI_CONFIG=\"" + tmpConfigFileName + "\"\n" + tmpRCFileName := tmpDirName + "/.bashrc" + + err = ioutil.WriteFile(tmpRCFileName, []byte(rcFile), 0760) + if err != nil { + return err + } + + logger.Debug("temporary shell profile loaded", zap.String("path", tmpRCFileName)) + e := exec.Command(shell, "-rcfile", tmpRCFileName, "-i") + if shell == "zsh" { + env := os.Environ() + env = append(env, fmt.Sprintf("ZDOTDIR=%s", tmpDirName)) + e = exec.Command(shell, "-i") + e.Env = env + } else if shell == "cmd.exe" { + env := os.Environ() + env = append(env, fmt.Sprintf("JCLI_CONFIG=%s", tmpConfigFileName)) + e = exec.Command(shell) + e.Env = env + } + + e.Stdout = cmd.OutOrStdout() + e.Stderr = cmd.OutOrStderr() + e.Stdin = os.Stdin + err = e.Run() + if deleteError := os.RemoveAll(tmpDirName); deleteError != nil { + panic(err) + } + return err + }, +} + +func createNewBashPrompt(prompt string) string { + if prompt == "" { + return "'[\\u@\\h \\W jcli> ]\\$ '" + } + if prompt[0] == '"' { + return prompt[0:1] + "jcli> " + prompt[1:] + } + if prompt[0] == '\'' { + return prompt[0:1] + "jcli> " + prompt[1:] + } + return "'jcli> " + prompt + "'" +} diff --git a/app/i18n/jcli/zh_CN/LC_MESSAGES/jcli.po b/app/i18n/jcli/zh_CN/LC_MESSAGES/jcli.po index e28634b..a1099eb 100644 --- a/app/i18n/jcli/zh_CN/LC_MESSAGES/jcli.po +++ b/app/i18n/jcli/zh_CN/LC_MESSAGES/jcli.po @@ -114,6 +114,10 @@ msgstr "Jenkins CLI (jcli) 管理你的 Jenkins" msgid "Whether skip the previous command hook" msgstr "是否跳过前置命令钩子" +#: app/cmd/shell.go:49 app/cmd/shell.go:50 +msgid "Create a sub shell so that changes to a specific Jenkins remain local to the shell." +msgstr "创建一个子 shell 使得对配置的修改保持在一个上下文中" + #: app/cmd/plugin.go:21 app/cmd/plugin.go:22 msgid "Manage the plugins of Jenkins" msgstr "管理 Jenkins 的插件" @@ -168,7 +172,7 @@ msgstr "Jenkins 的令牌" #: app/cmd/config_add.go:32 msgid "ProxyAuth of the Jenkins" -msgstr "" +msgstr "Jenkins 链接的代理认证" #: app/cmd/center_watch.go:27 msgid "The watch will be continue util Jenkins needs restart" @@ -224,7 +228,7 @@ msgstr "设置更新中心为一个镜像地址" #: app/cmd/plugin_search.go:33 app/cmd/plugin_search.go:34 msgid "Print the plugins of your Jenkins" -msgstr "" +msgstr "列出你的 Jenkins 中的插件" #: app/cmd/plugin_upload.go:39 msgid "Whether show the upload progress" diff --git a/go.mod b/go.mod index 99ecf12..af29181 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/gosexy/gettext v0.0.0-20160830220431-74466a0a0c4a // indirect github.com/gosuri/uilive v0.0.3 // indirect github.com/gosuri/uiprogress v0.0.1 + github.com/imdario/mergo v0.3.8 // indirect github.com/jessevdk/go-flags v1.4.0 // indirect github.com/mattn/go-isatty v0.0.10 // indirect github.com/mitchellh/go-homedir v1.1.0 @@ -21,5 +22,7 @@ require ( github.com/onsi/gomega v1.7.1 github.com/spf13/cobra v0.0.5 go.uber.org/zap v1.13.0 + golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c // indirect + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect gopkg.in/yaml.v2 v2.2.5 ) diff --git a/go.sum b/go.sum index e54b81c..03b3fbd 100644 --- a/go.sum +++ b/go.sum @@ -121,6 +121,8 @@ github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= @@ -242,9 +244,9 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEa go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.12.0 h1:dySoUQPFBGj6xwjmBzageVL8jGi8uxc6bEmJQjA06bw= go.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -283,6 +285,8 @@ golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c h1:HjRaKPaiWks0f5tA6ELVF7ZfqSppfPwOEEAvsrKUTO4= +golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -317,6 +321,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -363,11 +369,11 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= -- GitLab