This repository has been archived by the owner on Jan 30, 2023. It is now read-only.
/
launcher.go
223 lines (179 loc) · 7.63 KB
/
launcher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// Copyright 2014 The jenkins-client-launcher Authors. All rights reserved.
// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
package launcher
import (
"flag"
"os"
"path/filepath"
"fmt"
"regexp"
"net/http"
"io"
"time"
"github.com/jkellerer/jenkins-client-launcher/launcher/modes"
"github.com/jkellerer/jenkins-client-launcher/launcher/util"
"github.com/jkellerer/jenkins-client-launcher/launcher/environment"
)
const (
ConfigName = "launcher.config"
AppName = "Jenkins Client Launcher"
AppVersion = "0.3"
AppDescription = `
This application attempts to provide a stable runtime environment for a Jenkins client.
Regardless of the run mode, clients are started in 'user-mode' inheriting user & environment of the caller.
Functionality is controlled via CLI options and '%s' which is created in the current working directory when missing.
Usage %s [options]
Options:
`)
var AppImagePath = ""
// 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()
}
}
// 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
}
}
}
// 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))
}
}
}
// 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;
}