root.go 8.4 KB
Newer Older
LinuxSuRen's avatar
LinuxSuRen 已提交
1 2 3 4
package cmd

import (
	"fmt"
LinuxSuRen's avatar
LinuxSuRen 已提交
5
	"io"
6
	"log"
LinuxSuRen's avatar
LinuxSuRen 已提交
7
	"os"
LinuxSuRen's avatar
LinuxSuRen 已提交
8
	"os/exec"
LinuxSuRen's avatar
LinuxSuRen 已提交
9
	"regexp"
LinuxSuRen's avatar
LinuxSuRen 已提交
10
	"strings"
LinuxSuRen's avatar
LinuxSuRen 已提交
11

LinuxSuRen's avatar
LinuxSuRen 已提交
12 13
	"github.com/jenkins-zh/jenkins-cli/app/health"

14
	"github.com/jenkins-zh/jenkins-cli/app/i18n"
15 16
	"github.com/jenkins-zh/jenkins-cli/util"

17 18
	"github.com/jenkins-zh/jenkins-cli/client"

LinuxSuRen's avatar
LinuxSuRen 已提交
19
	"github.com/spf13/cobra"
20
	"go.uber.org/zap"
LinuxSuRen's avatar
LinuxSuRen 已提交
21 22
)

23 24
var logger *zap.Logger

LinuxSuRen's avatar
LinuxSuRen 已提交
25
// RootOptions is a global option for whole cli
26
type RootOptions struct {
27 28 29
	ConfigFile string
	Jenkins    string
	Debug      bool
30

31 32 33 34 35 36 37
	URL                string
	Username           string
	Token              string
	InsecureSkipVerify bool
	Proxy              string
	ProxyAuth          string

LinuxSuRen's avatar
LinuxSuRen 已提交
38 39
	Doctor bool

40
	LoggerLevel string
41 42
}

LinuxSuRen's avatar
LinuxSuRen 已提交
43 44 45 46
var healthCheckRegister = &health.CheckRegister{
	Member: make(map[string]health.CommandHealth, 0),
}

LinuxSuRen's avatar
LinuxSuRen 已提交
47
var rootCmd = &cobra.Command{
48
	Use:   "jcli",
49 50 51 52
	Short: i18n.T("Jenkins CLI written by golang which could help you with your multiple Jenkins"),
	Long: `Jenkins CLI written by golang which could help you with your multiple Jenkins,

We'd love to hear your feedback at https://github.com/jenkins-zh/jenkins-cli/issues`,
53 54
	PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
		if logger, err = util.InitLogger(rootOptions.LoggerLevel); err == nil {
55
			client.SetLogger(logger)
LinuxSuRen's avatar
LinuxSuRen 已提交
56 57 58 59
		} else {
			return
		}

LinuxSuRen's avatar
LinuxSuRen 已提交
60 61 62 63
		if needReadConfig(cmd) {
			if rootOptions.ConfigFile == "" {
				rootOptions.ConfigFile = os.Getenv("JCLI_CONFIG")
			}
LinuxSuRen's avatar
LinuxSuRen 已提交
64

LinuxSuRen's avatar
LinuxSuRen 已提交
65 66
			logger.Debug("read config file", zap.String("path", rootOptions.ConfigFile))
			if rootOptions.ConfigFile == "" {
67
				err = loadDefaultConfig()
LinuxSuRen's avatar
LinuxSuRen 已提交
68
			} else {
69
				err = loadConfig(rootOptions.ConfigFile)
LinuxSuRen's avatar
LinuxSuRen 已提交
70
			}
LinuxSuRen's avatar
LinuxSuRen 已提交
71 72
		}

LinuxSuRen's avatar
LinuxSuRen 已提交
73 74 75 76 77
		if err == nil {
			config = getConfig()
			if config != nil {
				// set Header Accept-Language
				client.SetLanguage(config.Language)
LinuxSuRen's avatar
LinuxSuRen 已提交
78 79
			}

LinuxSuRen's avatar
LinuxSuRen 已提交
80
			err = rootOptions.RunDiagnose(cmd)
81
		}
82
		return
83
	},
84
	BashCompletionFunction: jcliBashCompletionFunc,
LinuxSuRen's avatar
LinuxSuRen 已提交
85 86 87 88 89
}

func needReadConfig(cmd *cobra.Command) bool {
	ignoreConfigLoad := []string{
		"config.generate",
90
		//"center.start", // relay on the config when find a mirror
91
		"cwp",
LinuxSuRen's avatar
LinuxSuRen 已提交
92
		"version",
93
		"completion",
94
		"doc",
LinuxSuRen's avatar
LinuxSuRen 已提交
95 96 97 98 99 100
	}
	configPath := getCmdPath(cmd)

	for _, item := range ignoreConfigLoad {
		if item == configPath {
			return false
101
		}
LinuxSuRen's avatar
LinuxSuRen 已提交
102 103
	}
	return true
LinuxSuRen's avatar
LinuxSuRen 已提交
104 105
}

LinuxSuRen's avatar
LinuxSuRen 已提交
106 107 108 109 110 111
// RunDiagnose run the diagnose for a specific command
func (o *RootOptions) RunDiagnose(cmd *cobra.Command) (err error) {
	if !o.Doctor {
		return
	}
	path := getCmdPath(cmd)
112
	logger.Debug("start to run diagnose", zap.String("path", path))
LinuxSuRen's avatar
LinuxSuRen 已提交
113 114 115 116 117 118 119 120 121 122 123

	for k, v := range healthCheckRegister.Member {
		if ok, _ := regexp.MatchString(k, path); ok {
			err = v.Check()
			break
		}

	}
	return
}

LinuxSuRen's avatar
LinuxSuRen 已提交
124
// Execute will execute the command
LinuxSuRen's avatar
LinuxSuRen 已提交
125 126 127 128 129 130
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		os.Exit(1)
	}
}

131
var rootOptions RootOptions
LinuxSuRen's avatar
LinuxSuRen 已提交
132 133

func init() {
134 135 136 137
	rootCmd.PersistentFlags().StringVarP(&rootOptions.ConfigFile, "configFile", "", "",
		i18n.T("An alternative config file"))
	rootCmd.PersistentFlags().StringVarP(&rootOptions.Jenkins, "jenkins", "j", "",
		i18n.T("Select a Jenkins server for this time"))
138
	rootCmd.PersistentFlags().BoolVarP(&rootOptions.Debug, "debug", "", false, "Print the output into debug.html")
139 140
	rootCmd.PersistentFlags().StringVarP(&rootOptions.LoggerLevel, "logger-level", "", "warn",
		"Logger level which could be: debug, info, warn, error")
LinuxSuRen's avatar
LinuxSuRen 已提交
141 142
	rootCmd.PersistentFlags().BoolVarP(&rootOptions.Doctor, "doctor", "", false,
		i18n.T("Run the diagnose for current command"))
143 144 145 146 147 148 149 150 151 152 153 154 155 156

	rootCmd.PersistentFlags().StringVarP(&rootOptions.URL, "url", "", "",
		i18n.T("The URL of Jenkins"))
	rootCmd.PersistentFlags().StringVarP(&rootOptions.Username, "username", "", "",
		i18n.T("The username of Jenkins"))
	rootCmd.PersistentFlags().StringVarP(&rootOptions.Token, "token", "", "",
		i18n.T("The token of Jenkins"))
	rootCmd.PersistentFlags().BoolVarP(&rootOptions.InsecureSkipVerify, "insecureSkipVerify", "", true,
		i18n.T("If skip insecure skip verify"))
	rootCmd.PersistentFlags().StringVarP(&rootOptions.Proxy, "proxy", "", "",
		i18n.T("The proxy of connection to Jenkins"))
	rootCmd.PersistentFlags().StringVarP(&rootOptions.ProxyAuth, "proxy-auth", "", "",
		i18n.T("The auth of proxy of connection to Jenkins"))

157
	rootCmd.SetOut(os.Stdout)
LinuxSuRen's avatar
LinuxSuRen 已提交
158 159
}

LinuxSuRen's avatar
LinuxSuRen 已提交
160 161
func getCurrentJenkinsFromOptions() (jenkinsServer *JenkinsServer) {
	jenkinsOpt := rootOptions.Jenkins
LinuxSuRen's avatar
LinuxSuRen 已提交
162

LinuxSuRen's avatar
LinuxSuRen 已提交
163 164 165 166 167
	if jenkinsOpt == "" {
		jenkinsServer = getCurrentJenkins()
	} else {
		jenkinsServer = findJenkinsByName(jenkinsOpt)
	}
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

	// take URL from options if it's not empty
	if jenkinsServer == nil && rootOptions.URL != "" {
		jenkinsServer = &JenkinsServer{}
	}

	if jenkinsServer != nil {
		if rootOptions.URL != "" {
			jenkinsServer.URL = rootOptions.URL
		}

		if rootOptions.Username != "" {
			jenkinsServer.UserName = rootOptions.Username
		}

		if rootOptions.Token != "" {
			jenkinsServer.Token = rootOptions.Token
		}
	}
LinuxSuRen's avatar
LinuxSuRen 已提交
187 188 189
	return
}

190
// Deprecated, please use getCurrentJenkinsFromOptions instead of it
LinuxSuRen's avatar
LinuxSuRen 已提交
191 192
func getCurrentJenkinsFromOptionsOrDie() (jenkinsServer *JenkinsServer) {
	if jenkinsServer = getCurrentJenkinsFromOptions(); jenkinsServer == nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
193
		log.Fatal("Cannot found Jenkins by", rootOptions.Jenkins)
LinuxSuRen's avatar
LinuxSuRen 已提交
194 195 196
	}
	return
}
LinuxSuRen's avatar
LinuxSuRen 已提交
197

LinuxSuRen's avatar
LinuxSuRen 已提交
198 199 200 201 202 203 204 205 206 207 208 209 210 211
func getCmdPath(cmd *cobra.Command) string {
	current := cmd.Use
	if cmd.HasParent() {
		parentName := getCmdPath(cmd.Parent())
		if parentName == "" {
			return current
		}

		return fmt.Sprintf("%s.%s", parentName, current)
	}
	// don't need the name of root cmd
	return ""
}

LinuxSuRen's avatar
LinuxSuRen 已提交
212
func executePreCmd(cmd *cobra.Command, _ []string, writer io.Writer) (err error) {
LinuxSuRen's avatar
LinuxSuRen 已提交
213 214
	config := getConfig()
	if config == nil {
215
		err = fmt.Errorf("cannot find config file")
LinuxSuRen's avatar
LinuxSuRen 已提交
216 217 218 219
		return
	}

	path := getCmdPath(cmd)
LinuxSuRen's avatar
LinuxSuRen 已提交
220 221
	for _, hook := range config.PreHooks {
		if path != hook.Path {
LinuxSuRen's avatar
LinuxSuRen 已提交
222 223 224
			continue
		}

LinuxSuRen's avatar
LinuxSuRen 已提交
225 226 227 228 229 230 231 232 233 234
		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 {
235
		err = fmt.Errorf("cannot find config file")
LinuxSuRen's avatar
LinuxSuRen 已提交
236 237 238 239 240 241 242 243 244 245
		return
	}

	path := getCmdPath(cmd)
	for _, hook := range config.PostHooks {
		if path != hook.Path {
			continue
		}

		if err = execute(hook.Command, writer); err != nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
246 247
			return
		}
LinuxSuRen's avatar
LinuxSuRen 已提交
248
	}
LinuxSuRen's avatar
LinuxSuRen 已提交
249
	return
LinuxSuRen's avatar
LinuxSuRen 已提交
250 251
}

LinuxSuRen's avatar
LinuxSuRen 已提交
252
func execute(command string, writer io.Writer) (err error) {
LinuxSuRen's avatar
LinuxSuRen 已提交
253 254
	array := strings.Split(command, " ")
	cmd := exec.Command(array[0], array[1:]...)
255 256 257 258 259 260 261 262
	if err = cmd.Start(); err == nil {
		if err = cmd.Wait(); err == nil {
			var data []byte
			if data, err = cmd.Output(); err == nil {
				_, _ = writer.Write(data)
			}
		}
	}
LinuxSuRen's avatar
LinuxSuRen 已提交
263
	return
LinuxSuRen's avatar
LinuxSuRen 已提交
264
}
265 266 267 268 269

const (
	jcliBashCompletionFunc = `__plugin_name_parse_get()
{
    local jcli_output out
270 271
    if jcli_output=$(jcli plugin list --filter HasUpdate=true --no-headers --filter ShortName="$1" 2>/dev/null); then
        out=($(echo "${jcli_output}" | awk '{print $1}'))
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
        COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
    fi
}

__jcli_get_plugin_name()
{
    __plugin_name_parse_get
    if [[ $? -eq 0 ]]; then
        return 0
    fi
}

__config_name_parse_get()
{
    local jcli_output out
    if jcli_output=$(jcli config list --no-headers 2>/dev/null); then
288
        out=($(echo "${jcli_output}" | awk '{print $1}'))
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
        COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
    fi
}

__jcli_get_config_name()
{
    __config_name_parse_get
    if [[ $? -eq 0 ]]; then
        return 0
    fi
}

__job_name_parse_get()
{
    local jcli_output out
304
    if jcli_output=$(jcli job search --columns URL --no-headers "$cur" 2>/dev/null); then
305 306 307 308 309 310 311 312 313 314 315 316 317
        out=($(echo "${jcli_output}"))
        COMPREPLY=( ${out} )
    fi
}

__jcli_get_job_name()
{
    __job_name_parse_get
    if [[ $? -eq 0 ]]; then
        return 0
    fi
}

318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
__computer_name_parse_get()
{
    local jcli_output out
    if jcli_output=$(jcli computer list --no-headers --columns DisplayName 2>/dev/null); then
        out=($(echo "${jcli_output}"))
        COMPREPLY=( ${out} )
    fi
}

__jcli_get_computer_name()
{
    __computer_name_parse_get
    if [[ $? -eq 0 ]]; then
        return 0
    fi
}

335 336 337 338 339 340
__jcli_custom_func() {
    case ${last_command} in
        jcli_plugin_upgrade | jcli_plugin_uninstall)
            __jcli_get_plugin_name
            return
            ;;
LinuxSuRen's avatar
LinuxSuRen 已提交
341
        jcli_open | jcli_config_select | jcli_config_remove | jcli_shell)
342 343 344
            __jcli_get_config_name
            return
            ;;
345 346 347 348
        jcli_computer_delete | jcli_computer_delete | jcli_computer_launch)
            __jcli_get_computer_name
            return
            ;;
349 350 351 352 353 354 355 356 357 358
        jcli_job_build | jcli_job_stop | jcli_job_log | jcli_job_delete | jcli_job_history | jcli_job_artifact | jcli_job_input)
            __jcli_get_job_name
            return
            ;;
        *)
            ;;
    esac
}
`
)