blob: 2124768c12b4941217c0a9506594827db48bb64e [file] [log] [blame]
Benjamin Copeland4e096712020-08-11 16:09:55 +01001/*
2 Configure Yet Another Docker clouds provided by the Yet Another Docker
3 Plugin. This Jenkins Script Console script makes it easy to maintain large
4 amounts of configured clouds.
5
6 Note: This script deletes all yet another docker cloud configurations from
7 Jenkins before configuring the clouds. It does not affect in-process
8 builds. However, if there are previously configured yet another docker
9 clouds then they will be removed.
10
11 Yet Another Docker Plugin 0.1.0-rc31
12 */
13
14import com.github.kostyasha.yad.DockerCloud
15import com.github.kostyasha.yad.DockerConnector
16import com.github.kostyasha.yad.DockerContainerLifecycle
17import com.github.kostyasha.yad.DockerSlaveTemplate
18import com.github.kostyasha.yad.commons.DockerCreateContainer
19import com.github.kostyasha.yad.commons.DockerImagePullStrategy
20import com.github.kostyasha.yad.commons.DockerPullImage
21import com.github.kostyasha.yad.commons.DockerRemoveContainer
22import com.github.kostyasha.yad.commons.DockerSSHConnector
23import com.github.kostyasha.yad.commons.DockerStopContainer
24import com.github.kostyasha.yad.launcher.DockerComputerJNLPLauncher
25import com.github.kostyasha.yad.launcher.DockerComputerLauncher
26import com.github.kostyasha.yad.launcher.DockerComputerSSHLauncher
27import com.github.kostyasha.yad.other.ConnectorType
28import com.github.kostyasha.yad.strategy.DockerOnceRetentionStrategy
29import hudson.plugins.sshslaves.verifiers.NonVerifyingKeyVerificationStrategy
30import hudson.plugins.sshslaves.verifiers.SshHostKeyVerificationStrategy
31
32import hudson.model.Node
33import hudson.slaves.EnvironmentVariablesNodeProperty
34import hudson.slaves.NodeProperty
35import hudson.slaves.RetentionStrategy
36import hudson.tools.ToolLocationNodeProperty
37import jenkins.model.Jenkins
38import net.sf.json.JSONArray
39import net.sf.json.JSONObject
40
41/*
42 Configure the Yet Another Docker Plugin via this clouds_yadocker variable.
43
44 This variable can be removed and referenced for other configurations. All
45 values are optional with defaults set.
46 */
47
48JSONArray clouds_yadocker = [ {% for host in hosts %}
49 [
50 cloud_name: '{{host.cloud_name}}',
51 docker_url: '{{host.docker_url}}',
52 docker_api_version: '{{host.docker_api_version}}',
53 host_credentials_id: '{{host.host_credentials_id}}',
54 //valid values: netty, jersey
55 connection_type: '{{host.connection_type}}',
56 connect_timeout: '{{host.connect_timeout}}',
57 max_containers: '{{host.max_containers}}',
58 docker_templates: [
59 // List item of templates {% for docker in host.docker_templates %}
60 [
61 max_instances: '{{docker.max_instances}}',
62 //DOCKER CONTAINER LIFECYCLE
63 docker_image_name: '{{docker.docker_image_name}}',
64 //PULL IMAGE SETTINGS
65 //valid values: pull_latest, pull_always, pull_once, pull_never
66 pull_strategy: '{{docker.pull_strategy}}',
67 pull_registry_credentials_id: '{{docker.pull_registry_credentials_id}}',
68 //CREATE CONTAINER SETTINGS
69 docker_command: '{{docker.docker_command}}',
70 hostname: '{{docker.hostname}}',
71 dns: '{{docker.dns|default('8.8.8.8')}}',
72 //volumes can be a string or list of strings
73 volumes: '{{docker.volumes}}',
74 //volumes_from can be a string or list of strings
75 //volumes_from: '{{docker.volumes_from}}',
76 //environment can be a string or list of strings
77 environment: '{{docker.environment}}',
Benjamin Copelanda650a7a2022-03-03 14:25:46 +000078 port_bindings: '{{docker.port_bindings|default('0.0.0.0::22')}}',
Benjamin Copeland4e096712020-08-11 16:09:55 +010079 bind_all_declared_ports: '{{docker.bind_all_declared_ports}}',
80 //0 is unlimited
81 memory_limit_in_mb: '{{docker.memory_limit_in_mb}}',
82 //0 is unlimited
83 cpu_shares: '{{docker.cpu_shares}}',
84 run_container_privileged: '{{docker.run_container_privileged}}',
85 allocate_pseudo_tty: '{{docker.allocate_pseudo_tty}}',
86 mac_address: '{{docker.mac_address}}',
87 //extra_hosts can be a string or list of strings
88 extra_hosts: '{{docker.extra_hosts}}',
89 network_mode: '{{docker.network_mode}}',
90 //devices can be a string or list of strings
91 devices: '{{docker.devices}}',
92 cpuset_constraint_cpus: '{{docker.cpuset_constraint_cpus}}',
93 cpuset_constraint_mems: '{{docker.cpuset_constraint_mems}}',
94 //links can be a string or list of strings
95 links: '{{docker.links}}',
96 //STOP CONTAINER SETTINGS
97 stop_container_timeout: '{{docker.stop_container_timeout}}',
98 //STOP CONTAINER SETTINGS
99 remove_volumes: '{{docker.remove_volumes}}',
100 force_remove_containers: '{{docker.force_remove_containers}}',
101 //JENKINS SLAVE CONFIG
102 remote_fs_root: '{{docker.remote_fs_root|default('/home/buildslave')}}',
103 labels: '{{docker.labels}}',
104 //valid values: exclusive or normal
105 usage: '{{docker.usage}}',
106 availability_strategy: '{{docker.availability_strategy}}',
107 availability_idle_timeout: '{{docker.availability_idle_timeout}}',
108 executors: '{{docker.executors}}',
109 //LAUNCH METHOD
110 //valid values: launch_ssh or launch_jnlp
111 {%if docker.launch_method == 'launch_ssh' %}
112 launch_method: 'launch_ssh',
113 //settings specific to launch_ssh (you only need one or the other)
114 launch_ssh_credentials_id: '{{docker.ssh.launch_ssh_credentials_id}}',
115 launch_ssh_port: '{{docker.ssh.launch_ssh_port|default('22')}}',
116 launch_ssh_java_path: '{{docker.ssh.launch_ssh_java_path}}',
117 launch_ssh_jvm_options: '{{docker.ssh.launch_ssh_jvm_options}}',
118 launch_ssh_prefix_start_slave_command: '{{docker.ssh.launch_ssh_prefix_start_slave_command}}',
119 launch_ssh_suffix_start_slave_command: '{{docker.ssh.launch_ssh_suffix_start_slave_command}}',
120 launch_ssh_connection_timeout: '{{docker.ssh.launch_ssh_connection_timeout}}',
121 launch_ssh_max_num_retries: '{{docker.ssh.launch_ssh_max_num_retries}}',
122 launch_ssh_time_wait_between_retries: '{{docker.ssh.launch_ssh_time_wait_between_retries}}',
123 {%elif docker.launch_method == 'launch_jnlp' %}
124 launch_method: 'launch_jnlp',
125 //settings specific to launch_jnlp
126 launch_jnlp_linux_user: '{{docker.jnlp.launch_jnlp_linux_user}}',
127 launch_jnlp_lauch_timeout: '{{docker.jnlp.launch_jnlp_lauch_timeout}}',
128 launch_jnlp_slave_jar_options: '{{docker.jnlp.launch_jnlp_slave_jar_options}}',
129 launch_jnlp_slave_jvm_options: '{{docker.jnlp.launch_jnlp_slave_jvm_options}}',
130 launch_jnlp_different_jenkins_master_url: '{{docker.jnlp.launch_jnlp_different_jenkins_master_url}}',
131 launch_jnlp_ignore_certificate_check: '{{docker.jnlp.launch_jnlp_ignore_certificate_check}}',
132 {%endif%}
133 //NODE PROPERTIES
134 //environment_variables is a HashMap of key/value pairs
Benjamin Copeland5a08ae32020-08-28 13:47:42 +0100135 environment_variables: [ {% for env in docker.environment_variables %} {% for name, value in env.items() %}
136 '{{name}}': '{{value}}',
137{% endfor %}{% endfor %} ],
Benjamin Copeland4e096712020-08-11 16:09:55 +0100138 //tool location key/value pairs from https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/tools/ToolLocationNodeProperty.java
139 //The key is type@name = home where type is typically the class name of the tool and name is the name given in the Global Tools configuration.
140 //For example let's say you have a global tool configuration named OracleJDK8 for JDK installations
141 //tool_locations would be something like ['hudson.model.JDK$DescriptorImpl@OracleJDK8': '/path/to/java_home']
142 //If you're unsure of the tool@name then check config.xml where YADocker configurations are saved.
143 tool_locations: '{{docker.tool_locations}}',
144 remote_fs_root_mapping: '{{docker.remote_fs_root_mapping}}'
145
146 ],
147{% endfor %}
148 ],
149 ],
150{% endfor %}
151 ] as JSONArray
152
153//detect an existing global tool
154def detectGlobalToolExists(String location) {
155 def (toolType, toolName) = location.split('@')
156 boolean found_installation = Jenkins.instance.getExtensionList(toolType)[0].installations.findAll { it.name == toolName } as boolean
157 return found_installation
158}
159
160//return a launcher
161def selectLauncher(String launcherType, JSONObject obj) {
162 switch(launcherType) {
163 case 'launch_ssh':
164 def jenkinsCredentials = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
165 com.cloudbees.plugins.credentials.Credentials.class,
166 Jenkins.instance,
167 null,
168 null
169 );
170 def launch_credentials = null
171 for (creds in jenkinsCredentials) {
172 if(creds.id == obj.optString('launch_ssh_credentials_id')){
173 launch_credentials = creds;
174 }
175 }
176 DockerSSHConnector sshConnector = new DockerSSHConnector(obj.optInt('launch_ssh_port', 22),
177 launch_credentials,
178 obj.optString('launch_ssh_credentials_id'),
179 obj.optString('launch_ssh_jvm_options'),
180 obj.optString('launch_ssh_java_path'),
181 null,
182 obj.optString('launch_ssh_prefix_start_slave_command'),
183 obj.optString('launch_ssh_suffix_start_slave_command'),
184 obj.optInt('launch_ssh_connection_timeout'),
185 obj.optInt('launch_ssh_max_num_retries'),
186 obj.optInt('launch_ssh_time_wait_between_retries'),
187 new NonVerifyingKeyVerificationStrategy()
188 )
189 return new DockerComputerSSHLauncher(sshConnector)
190 case 'launch_jnlp':
191 DockerComputerJNLPLauncher dockerComputerJNLPLauncher = new DockerComputerJNLPLauncher()
192 dockerComputerJNLPLauncher.setUser(obj.optString('launch_jnlp_linux_user','jenkins'))
193 dockerComputerJNLPLauncher.setLaunchTimeout(obj.optLong('launch_jnlp_lauch_timeout', 120L))
194 dockerComputerJNLPLauncher.setSlaveOpts(obj.optString('launch_jnlp_slave_jar_options'))
195 dockerComputerJNLPLauncher.setJvmOpts(obj.optString('launch_jnlp_slave_jvm_options'))
196 dockerComputerJNLPLauncher.setJenkinsUrl(obj.optString('launch_jnlp_different_jenkins_master_url'))
197 dockerComputerJNLPLauncher.setNoCertificateCheck(obj.optBoolean('launch_jnlp_ignore_certificate_check', false))
198 return dockerComputerJNLPLauncher
199 default:
200 return null
201 }
202}
203
204//create a new instance of the DockerCloud class from a JSONObject
205def newDockerCloud(JSONObject obj) {
206 DockerConnector connector = new DockerConnector(obj.optString('docker_url', 'tcp://localhost:2375'))
207 if(obj.optInt('connect_timeout')) {
208 connector.setConnectTimeout(obj.optInt('connect_timeout'))
209 }
210 connector.setApiVersion(obj.optString('docker_api_version'))
211 connector.setCredentialsId(obj.optString('host_credentials_id'))
212 //select connection_type
213 List<String> connection_types = ['NETTY', 'JERSEY']
214 String connection_type_default = 'JERSEY'
215 String user_selected_connection_type = obj.optString('connection_type', connection_type_default).toUpperCase()
216 String connection_type = (user_selected_connection_type in connection_types)? user_selected_connection_type : connection_type_default
217 connector.setConnectorType(ConnectorType."${connection_type}")
218
219 DockerCloud cloud = new DockerCloud(obj.optString('cloud_name'),
220 bindJSONToList(DockerSlaveTemplate.class, obj.opt('docker_templates')),
221 obj.optInt('max_containers', 1),
222 connector)
223
224 return cloud
225}
226
227def newDockerSlaveTemplate(JSONObject obj) {
228 //DockerPullImage
229 DockerPullImage pullImage = new DockerPullImage()
230 String pullStrategy = obj.optString('pull_strategy', 'PULL_LATEST').toUpperCase()
231 if(pullStrategy in ['PULL_LATEST', 'PULL_ALWAYS', 'PULL_ONCE', 'PULL_NEVER']) {
232 pullImage.setPullStrategy(DockerImagePullStrategy."${pullStrategy}")
233 } else {
234 pullImage.setPullStrategy(DockerImagePullStrategy.PULL_LATEST)
235 }
236 pullImage.setCredentialsId(obj.optString('pull_registry_credentials_id'))
237
238 //DockerCreateContainer
239 DockerCreateContainer createContainer = new DockerCreateContainer()
240 createContainer.setBindPorts(obj.optString('port_bindings'))
241 createContainer.setBindAllPorts(obj.optBoolean('bind_all_declared_ports', false))
242 createContainer.setDnsString(obj.optString('dns'))
243 createContainer.setHostname(obj.optString('hostname'))
244 if(obj.optLong('memory_limit_in_mb')) {
245 createContainer.setMemoryLimit(obj.optLong('memory_limit_in_mb'))
246 }
247 createContainer.setPrivileged(obj.optBoolean('run_container_privileged', true))
248 createContainer.setTty(obj.optBoolean('allocate_pseudo_tty', false))
249 if(obj.optJSONArray('volumes')) {
250 createContainer.setVolumes(obj.optJSONArray('volumes') as List<String>)
251 } else {
252 createContainer.setVolumesString(obj.optString('volumes'))
253 }
254 if(obj.optJSONArray('volumes_from')) {
255 createContainer.setVolumesFrom(obj.optJSONArray('volumes_from') as List<String>)
256 } else {
257 createContainer.setVolumesFromString(obj.optString('volumes_from'))
258 }
259 createContainer.setMacAddress(obj.optString('mac_address'))
260 if(obj.optInt('cpu_shares')) {
261 createContainer.setCpuShares(obj.optInt('cpu_shares'))
262 }
263 createContainer.setCommand(obj.optString('docker_command'))
264 if(obj.optJSONArray('environment')) {
265 createContainer.setEnvironment(obj.optJSONArray('environment') as List<String>)
266 } else {
267 createContainer.setEnvironmentString(obj.optString('environment'))
268 }
269 if(obj.optJSONArray('extra_hosts')) {
270 createContainer.setExtraHosts(obj.optJSONArray('extra_hosts') as List<String>)
271 } else {
272 createContainer.setExtraHostsString(obj.optString('extra_hosts'))
273 }
274 createContainer.setNetworkMode(obj.optString('network_mode'))
275 if(obj.optJSONArray('devices')) {
276 createContainer.setDevices(obj.optJSONArray('devices'))
277 } else {
278 createContainer.setDevicesString(obj.optString('devices'))
279 }
280 createContainer.setCpusetCpus(obj.optString('cpuset_constraint_cpus'))
281 createContainer.setCpusetMems(obj.optString('cpuset_constraint_mems'))
282 if(obj.optJSONArray('links')) {
283 createContainer.setLinks(obj.optJSONArray('links'))
284 } else {
285 createContainer.setLinksString(obj.optString('links'))
286 }
287
288 //DockerStopContainer
289 DockerStopContainer stopContainer = new DockerStopContainer()
290 stopContainer.setTimeout(obj.optInt('stop_container_timeout', 10))
291
292 //DockerRemoveContainer
293 DockerRemoveContainer removeContainer = new DockerRemoveContainer()
294 removeContainer.setRemoveVolumes(obj.optBoolean('remove_volumes', false))
295 removeContainer.setForce(obj.optBoolean('force_remove_containers', false))
296
297 //DockerContainerLifecycle
298 DockerContainerLifecycle dockerContainerLifecycle = new DockerContainerLifecycle()
299 dockerContainerLifecycle.setImage(obj.optString('docker_image_name'))
300 dockerContainerLifecycle.setPullImage(pullImage)
301 dockerContainerLifecycle.setCreateContainer(createContainer)
302 dockerContainerLifecycle.setStopContainer(stopContainer)
303 dockerContainerLifecycle.setRemoveContainer(removeContainer)
304
305 //DockerOnceRetentionStrategy
306 //this availability_strategy is for "run_once". We can customize it later
307 RetentionStrategy retentionStrategy = new DockerOnceRetentionStrategy(obj.optInt('availability_idle_timeout', 10))
308
309 //DockerComputerLauncher
310 //select a launch method from the list of available launch methods
311 List<String> launch_methods = ['launch_ssh', 'launch_jnlp']
312 String default_launch_method = 'launch_jnlp'
313 String user_selected_launch_method = obj.optString('launch_method', default_launch_method).toLowerCase()
314 String launch_method = (user_selected_launch_method in launch_methods)? user_selected_launch_method : default_launch_method
315 DockerComputerLauncher launcher = selectLauncher(launch_method, obj)
316
317 //DockerSlaveTemplate
318 DockerSlaveTemplate dockerSlaveTemplate = new DockerSlaveTemplate()
319 dockerSlaveTemplate.setDockerContainerLifecycle(dockerContainerLifecycle)
320 dockerSlaveTemplate.setLabelString(obj.optString('labels', 'docker'))
321 String node_usage = (obj.optString('usage', 'EXCLUSIVE').toUpperCase().equals('NORMAL'))? 'NORMAL' : 'EXCLUSIVE'
322 dockerSlaveTemplate.setMode(Node.Mode."${node_usage}")
323 dockerSlaveTemplate.setNumExecutors(obj.optInt('executors', 1))
324 dockerSlaveTemplate.setRetentionStrategy(retentionStrategy)
325 dockerSlaveTemplate.setLauncher(launcher)
326 dockerSlaveTemplate.setRemoteFs(obj.optString('remote_fs_root', '/srv/jenkins'))
327 dockerSlaveTemplate.setMaxCapacity(obj.optInt('max_instances', 1))
328 //dockerSlaveTemplate.setRemoteFsMapping(obj.optString('remote_fs_root_mapping'))
329 //dockerSlaveTemplate.remoteFsMapping = obj.optString('remote_fs_root_mapping')
330 //define NODE PROPERTIES
331 List<NodeProperty> nodeProperties = [] as List<NodeProperty>
332 if(obj.optJSONObject('environment_variables')) {
333 HashMap<String,String> env = obj.optJSONObject('environment_variables') as HashMap<String,String>
334 List<EnvironmentVariablesNodeProperty.Entry> envEntries = [] as List<EnvironmentVariablesNodeProperty.Entry>
335 (env.keySet() as String[]).each { var ->
336 envEntries << (new EnvironmentVariablesNodeProperty.Entry(var, env[var]))
337 }
338 //add environment_variables to nodeProperties
339 nodeProperties << (new EnvironmentVariablesNodeProperty(envEntries))
340 }
341 if(obj.optJSONObject('tool_locations')) {
342 HashMap<String,String> tool = obj.optJSONObject('tool_locations') as HashMap<String,String>
343 List<ToolLocationNodeProperty.ToolLocation> toolLocations = [] as List<ToolLocationNodeProperty.ToolLocation>
344 (tool.keySet() as String[]).each { location ->
345 //tool location is only valid if it contains a type i.e. @ symbol
346 if(location.contains('@') && detectGlobalToolExists(location)) {
347 toolLocations << (new ToolLocationNodeProperty.ToolLocation(location, tool[location]))
348 } else {
349 //alert the user they configured an invalid tool type or name in tool_locations
350 println "WARNING: Invalid tool: '${location}'. Format should be 'type@name' where both type and name already exist in global tool configurations."
351 }
352 }
353 nodeProperties << (new ToolLocationNodeProperty(toolLocations))
354 }
355 //set NODE PROPERTIES
356 if(nodeProperties) {
357 dockerSlaveTemplate.setNodeProperties(nodeProperties)
358 }
359 return dockerSlaveTemplate
360}
361
362def bindJSONToList(Class type, Object src) {
363 if(!(type == DockerCloud) && !(type == DockerSlaveTemplate)) {
364 throw new Exception("Must use DockerCloud or DockerSlaveTemplate class.")
365 }
366 //docker_array should be a DockerCloud or DockerSlaveTemplate
367 ArrayList<?> docker_array
368 if(type == DockerCloud){
369 docker_array = new ArrayList<DockerCloud>()
370 } else {
371 docker_array = new ArrayList<DockerSlaveTemplate>()
372 }
373 //cast the configuration object to a Docker instance which Jenkins will use in configuration
374 if (src instanceof JSONObject) {
375 //uses string interpolation to call a method
376 //e.g instead of newDockerCloud(src) we use instead...
377 docker_array.add("new${type.getSimpleName()}"(src))
378 } else if(src instanceof JSONArray) {
379 for (Object o : src) {
380 if (o instanceof JSONObject) {
381 docker_array.add("new${type.getSimpleName()}"(o))
382 }
383 }
384 }
385 return docker_array
386}
387
388if(!Jenkins.instance.isQuietingDown()) {
389 ArrayList<DockerCloud> clouds = new ArrayList<DockerCloud>()
390 clouds = bindJSONToList(DockerCloud.class, clouds_yadocker)
391 if(clouds.size() > 0) {
392{%if dryrun == True %}
393 clouds*.name.each { cloudName ->
394 println "DRYRUN: Configured Yet Another Docker cloud ${cloudName}"
395 }
396{%else%}
397 dockerConfigUpdated = true
398 Jenkins.instance.clouds.removeAll(DockerCloud)
399 Jenkins.instance.clouds.addAll(clouds)
400 clouds*.name.each { cloudName ->
401 println "Configured Yet Another Docker cloud ${cloudName}"
402 }
403 Jenkins.instance.save()
404{%endif%}
405 } else {
406 println 'Nothing changed. No Yet Another docker clouds to configure.'
407 }
408} else {
409 println 'Shutdown mode enabled. Configure Yet Another Docker clouds SKIPPED.'
410}
411
412//return null so there's no result value in the script console
413null
414
415/*
416 The MIT License (MIT)
417
418 Copyright 2017 Sam Gleske - https://github.com/samrocketman/jenkins-bootstrap-jervis
419
420 Permission is hereby granted, free of charge, to any person obtaining a copy
421 of this software and associated documentation files (the "Software"), to
422 deal in the Software without restriction, including without limitation the
423 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
424 sell copies of the Software, and to permit persons to whom the Software is
425 furnished to do so, subject to the following conditions:
426
427 The above copyright notice and this permission notice shall be included in
428 all copies or substantial portions of the Software.
429
430 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
431 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
432 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
433 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
434 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
435 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
436 IN THE SOFTWARE.
437 */