// Loads the config file from disk or from the specified defaultConfig location // when either the local file is missing or overwriteWithInitial is true. func loadConfig(defaultConfig string, overwriteWithInitial bool) *util.Config { config := util.NewConfig(ConfigName) if len(defaultConfig) > 0 && (config.NeedsSave || overwriteWithInitial) { var err error var configSource io.ReadCloser defer func() { if configSource != nil { configSource.Close() } }() // If default config uses "." we search for it next to the location where the launcher executable resides. if defaultConfig == "." { defaultConfig = filepath.Join(filepath.Dir(AppImagePath), ConfigName) } if isHttpUrl, _ := regexp.MatchString("^(?i)http(s|)://.+", defaultConfig); isHttpUrl { util.Out("Downloading: %v", defaultConfig) var response *http.Response if response, err = http.Get(defaultConfig); err == nil { if response.StatusCode == 200 { configSource = response.Body } else { err = fmt.Errorf(response.Status) } } } else { util.Out("Copying: %v", defaultConfig) configSource, err = os.Open(defaultConfig) } if err != nil { panic(fmt.Sprintf("Failed loading %v;\nCause: %v; => exiting.", defaultConfig, err)) } if configFile, err := os.Create(ConfigName); err == nil { defer configFile.Close() if _, err = io.Copy(configFile, configSource); err == nil { config = util.NewConfig(ConfigName) } } else { panic(fmt.Sprintf("Failed creating initial %v from %v;\ncause: %v; => exiting.", ConfigName, defaultConfig, err)) } } return config }
// Runs the mode that is activated within the specified config instance, // returning false if the mode stopped due to a kill or interrupt and true when the mode stopped due to an error. func RunConfiguredMode(config *util.Config) bool { exitRequested := make(chan bool, 2) // Getting the configured run mode executableMode := GetConfiguredMode(config) util.Out("Starting mode %v", executableMode.Name()) callListeners(executableMode, ModeStarting, config) err := executableMode.Start(config) if err != nil { util.Out("ERROR: Failed to start mode '%v'; Cause: %v", executableMode.Name(), err) return false } else { callListeners(executableMode, ModeStarted, config) defer callListeners(executableMode, ModeStopped, config) } // Waiting on Interrupt or Kill and stop the run mode when received. signals := make(chan os.Signal, 1) signal.Notify(signals, os.Interrupt, os.Kill) go func() { sig := <-signals // This channel receives only Interrupt or Kill and blocks until one is received. util.Out("Received signal: %v, stopping...", sig) exitRequested <- true if executableMode.Status().Get() != ModeStopped { executableMode.Stop() } }() util.Out("STARTED mode %v", executableMode.Name()) // Waiting for the run mode to stop for executableMode.Status().Get() != ModeStopped { time.Sleep(time.Millisecond * 100) } exitRequested <- false signals <- os.Interrupt util.Out("STOPPED mode %v", executableMode.Name()) // Returning true if we want to re-run. isExistRequested := <-exitRequested <-exitRequested return !isExistRequested }
// Handles the working directory that is used. func handleWorkingDirectory(dir string) { wd, _ := os.Getwd() dir = filepath.FromSlash(dir) if len(dir) > 0 && dir != wd { util.Out("Changing working directory to %v", dir) if fi, err := os.Stat(dir); err != nil && os.IsNotExist(err) { util.Out("WARN: Working directory %v does not exist, creating it now.", dir) if err = os.MkdirAll(dir, os.ModeDir); err != nil { panic(fmt.Sprintf("Failed creating working directory %v, stopping here to prevent any damage. Cause: %v", dir, err)) } } else if !fi.IsDir() { panic(fmt.Sprintf("%v is not a directory, cannot change working directory, stopping here to prevent any damage. Cause: %v", dir, err)) } if err := os.Chdir(dir); err != nil { wd, _ = os.Getwd() panic(fmt.Sprintf("Failed changing working directory, stopping here to prevent any damage. Cause: %v ; CWD is %v", err, wd)) } } }
// Listens for key codes. func listenForKeyboardInput(config *util.Config, subsequentRestarts *int64) { var keyCode = make([]byte, 1) util.Out("Listening for keys: [%s]: Print Stacktrace | [%s]: Restart client.", "D+Return", "R+Return") for { if n, err := os.Stdin.Read(keyCode); err == nil && n == 1 { switch keyCode[0] { case 'r', 'R': *subsequentRestarts = 0 modes.GetConfiguredMode(config).Stop() case 'd', 'D': util.PrintAllStackTraces() } } else { return } } }
// Is the applications main run loop. func Run() { util.FlatOut("\n%s %s\n---------------------------", AppName, AppVersion) AppImagePath, _ = filepath.Abs(os.Args[0]) modeNames := make([]string, len(modes.AllModes)) for i, m := range modes.AllModes { modeNames[i] = m.Name() } help := flag.Bool("help", false, "Show this help.") autoStart := flag.Bool("autostart", false, "Automatically start this app when the OS runs (implies '-persist=true').") runMode := flag.String("runMode", "client", "Specifies the mode in which the launcher operates if not already set "+ "inside '"+ConfigName+"'. Supporte modes are "+fmt.Sprintf("%v", modeNames)+".") url := flag.String("url", "", "Specifies the URL to Jenkins server if not already set inside '"+ConfigName+"'.") name := flag.String("name", "", "Specifies the name of this node in Jenkins (defaults to [hostname] if not specified).") create := flag.Bool("create", false, "Enables the auto creation of a Jenkins node if it is missing.") secretKey := flag.String("secret", "", "Specifies the secret key to use in client mode when starting the Jenkins client.") acceptAnyCert := flag.Bool("anyCert", false, "Disabled cert verification for TLS connections with Jenkins (this is not secure at all).") dir := flag.String("directory", "", "Changes the current working directory before performing any other operations.") saveChanges := flag.Bool("persist", false, "Stores any CLI config overrides inside '"+ConfigName+"'.") defaultConfig := flag.String("defaultConfig", "", "Loads the initial config from the specified path or URL (http[s]). "+ "Does nothing when '"+ConfigName+"' exists already.") overwrite := flag.Bool("overwrite", false, "Overwrites '"+ConfigName+"' with the content from initial config "+ "(requires '-defaultConfig=...', implies '-persist=true').") flag.CommandLine.Init(AppName, flag.ContinueOnError) flag.CommandLine.SetOutput(os.Stdout) flag.Parse() handleWorkingDirectory(*dir) if alreadyRunning := CheckIfAlreadyRunning(); alreadyRunning { util.Out("WARN: Another launcher is already running with the same configuration. Exiting...") return } else { defer alreadyRunning.Close() } config := loadConfig(*defaultConfig, *overwrite) if len(*runMode) > 0 { config.RunMode = *runMode } if len(*url) > 0 { config.CIHostURI = *url } if len(*secretKey) > 0 { config.SecretKey = *secretKey } if len(*name) > 0 { config.ClientName = *name } if *create { config.CreateClientIfMissing = true } if *acceptAnyCert { config.CIAcceptAnyCert = true } saveConfigIfRequired := func() { if config.NeedsSave || *saveChanges || *autoStart || *overwrite { config.Save(ConfigName) } } if *help { saveConfigIfRequired() fmt.Printf(AppDescription, ConfigName, filepath.Base(os.Args[0])) flag.PrintDefaults() return } environment.RunPreparers(config) abort := !modes.GetConfiguredMode(config).IsConfigAcceptable(config) saveConfigIfRequired() if abort { return } restartCount := int64(0) sleepTimePerRestart := int64(time.Second * time.Duration(config.SleepTimeSecondsBetweenFailures)) runTimeAfterResettingRestartCount := time.Hour * 2 timeOfLastStart := time.Now() go listenForKeyboardInput(config, &restartCount) for modes.RunConfiguredMode(config) { util.FlatOut("\n:::::::::::::::::::::::::::::::::\n:: %25s ::\n:::::::::::::::::::::::::::::::::\n", "Restarting Jenkins Client") if timeOfLastStart.Before(time.Now().Add(-runTimeAfterResettingRestartCount)) { restartCount = 0 } if sleepTime := time.Duration(restartCount * sleepTimePerRestart); sleepTime > 0 { util.FlatOut("Sleeping %v seconds before restarting the client.\n\n", sleepTime.Seconds()) time.Sleep(sleepTime) } restartCount++ timeOfLastStart = time.Now() } }