| /* |
| Configure Yet Another Docker clouds provided by the Yet Another Docker |
| Plugin. This Jenkins Script Console script makes it easy to maintain large |
| amounts of configured clouds. |
| |
| Note: This script deletes all yet another docker cloud configurations from |
| Jenkins before configuring the clouds. It does not affect in-process |
| builds. However, if there are previously configured yet another docker |
| clouds then they will be removed. |
| |
| Yet Another Docker Plugin 0.1.0-rc31 |
| */ |
| |
| import com.github.kostyasha.yad.DockerCloud |
| import com.github.kostyasha.yad.DockerConnector |
| import com.github.kostyasha.yad.DockerContainerLifecycle |
| import com.github.kostyasha.yad.DockerSlaveTemplate |
| import com.github.kostyasha.yad.commons.DockerCreateContainer |
| import com.github.kostyasha.yad.commons.DockerImagePullStrategy |
| import com.github.kostyasha.yad.commons.DockerPullImage |
| import com.github.kostyasha.yad.commons.DockerRemoveContainer |
| import com.github.kostyasha.yad.commons.DockerSSHConnector |
| import com.github.kostyasha.yad.commons.DockerStopContainer |
| import com.github.kostyasha.yad.launcher.DockerComputerJNLPLauncher |
| import com.github.kostyasha.yad.launcher.DockerComputerLauncher |
| import com.github.kostyasha.yad.launcher.DockerComputerSSHLauncher |
| import com.github.kostyasha.yad.other.ConnectorType |
| import com.github.kostyasha.yad.strategy.DockerOnceRetentionStrategy |
| import hudson.plugins.sshslaves.verifiers.NonVerifyingKeyVerificationStrategy |
| import hudson.plugins.sshslaves.verifiers.SshHostKeyVerificationStrategy |
| |
| import hudson.model.Node |
| import hudson.slaves.EnvironmentVariablesNodeProperty |
| import hudson.slaves.NodeProperty |
| import hudson.slaves.RetentionStrategy |
| import hudson.tools.ToolLocationNodeProperty |
| import jenkins.model.Jenkins |
| import net.sf.json.JSONArray |
| import net.sf.json.JSONObject |
| |
| /* |
| Configure the Yet Another Docker Plugin via this clouds_yadocker variable. |
| |
| This variable can be removed and referenced for other configurations. All |
| values are optional with defaults set. |
| */ |
| |
| JSONArray clouds_yadocker = [ {% for host in hosts %} |
| [ |
| cloud_name: '{{host.cloud_name}}', |
| docker_url: '{{host.docker_url}}', |
| docker_api_version: '{{host.docker_api_version}}', |
| host_credentials_id: '{{host.host_credentials_id}}', |
| //valid values: netty, jersey |
| connection_type: '{{host.connection_type}}', |
| connect_timeout: '{{host.connect_timeout}}', |
| max_containers: '{{host.max_containers}}', |
| docker_templates: [ |
| // List item of templates {% for docker in host.docker_templates %} |
| [ |
| max_instances: '{{docker.max_instances}}', |
| //DOCKER CONTAINER LIFECYCLE |
| docker_image_name: '{{docker.docker_image_name}}', |
| //PULL IMAGE SETTINGS |
| //valid values: pull_latest, pull_always, pull_once, pull_never |
| pull_strategy: '{{docker.pull_strategy}}', |
| pull_registry_credentials_id: '{{docker.pull_registry_credentials_id}}', |
| //CREATE CONTAINER SETTINGS |
| docker_command: '{{docker.docker_command}}', |
| hostname: '{{docker.hostname}}', |
| dns: '{{docker.dns|default('8.8.8.8')}}', |
| //volumes can be a string or list of strings |
| volumes: '{{docker.volumes}}', |
| //volumes_from can be a string or list of strings |
| //volumes_from: '{{docker.volumes_from}}', |
| //environment can be a string or list of strings |
| environment: '{{docker.environment}}', |
| port_bindings: '{{docker.port_bindings|default('0.0.0.0::22')}}', |
| bind_all_declared_ports: '{{docker.bind_all_declared_ports}}', |
| //0 is unlimited |
| memory_limit_in_mb: '{{docker.memory_limit_in_mb}}', |
| //0 is unlimited |
| cpu_shares: '{{docker.cpu_shares}}', |
| run_container_privileged: '{{docker.run_container_privileged}}', |
| allocate_pseudo_tty: '{{docker.allocate_pseudo_tty}}', |
| mac_address: '{{docker.mac_address}}', |
| //extra_hosts can be a string or list of strings |
| extra_hosts: '{{docker.extra_hosts}}', |
| network_mode: '{{docker.network_mode}}', |
| //devices can be a string or list of strings |
| devices: '{{docker.devices}}', |
| cpuset_constraint_cpus: '{{docker.cpuset_constraint_cpus}}', |
| cpuset_constraint_mems: '{{docker.cpuset_constraint_mems}}', |
| //links can be a string or list of strings |
| links: '{{docker.links}}', |
| //STOP CONTAINER SETTINGS |
| stop_container_timeout: '{{docker.stop_container_timeout}}', |
| //STOP CONTAINER SETTINGS |
| remove_volumes: '{{docker.remove_volumes}}', |
| force_remove_containers: '{{docker.force_remove_containers}}', |
| //JENKINS SLAVE CONFIG |
| remote_fs_root: '{{docker.remote_fs_root|default('/home/buildslave')}}', |
| labels: '{{docker.labels}}', |
| //valid values: exclusive or normal |
| usage: '{{docker.usage}}', |
| availability_strategy: '{{docker.availability_strategy}}', |
| availability_idle_timeout: '{{docker.availability_idle_timeout}}', |
| executors: '{{docker.executors}}', |
| //LAUNCH METHOD |
| //valid values: launch_ssh or launch_jnlp |
| {%if docker.launch_method == 'launch_ssh' %} |
| launch_method: 'launch_ssh', |
| //settings specific to launch_ssh (you only need one or the other) |
| launch_ssh_credentials_id: '{{docker.ssh.launch_ssh_credentials_id}}', |
| launch_ssh_port: '{{docker.ssh.launch_ssh_port|default('22')}}', |
| launch_ssh_java_path: '{{docker.ssh.launch_ssh_java_path}}', |
| launch_ssh_jvm_options: '{{docker.ssh.launch_ssh_jvm_options}}', |
| launch_ssh_prefix_start_slave_command: '{{docker.ssh.launch_ssh_prefix_start_slave_command}}', |
| launch_ssh_suffix_start_slave_command: '{{docker.ssh.launch_ssh_suffix_start_slave_command}}', |
| launch_ssh_connection_timeout: '{{docker.ssh.launch_ssh_connection_timeout}}', |
| launch_ssh_max_num_retries: '{{docker.ssh.launch_ssh_max_num_retries}}', |
| launch_ssh_time_wait_between_retries: '{{docker.ssh.launch_ssh_time_wait_between_retries}}', |
| {%elif docker.launch_method == 'launch_jnlp' %} |
| launch_method: 'launch_jnlp', |
| //settings specific to launch_jnlp |
| launch_jnlp_linux_user: '{{docker.jnlp.launch_jnlp_linux_user}}', |
| launch_jnlp_lauch_timeout: '{{docker.jnlp.launch_jnlp_lauch_timeout}}', |
| launch_jnlp_slave_jar_options: '{{docker.jnlp.launch_jnlp_slave_jar_options}}', |
| launch_jnlp_slave_jvm_options: '{{docker.jnlp.launch_jnlp_slave_jvm_options}}', |
| launch_jnlp_different_jenkins_master_url: '{{docker.jnlp.launch_jnlp_different_jenkins_master_url}}', |
| launch_jnlp_ignore_certificate_check: '{{docker.jnlp.launch_jnlp_ignore_certificate_check}}', |
| {%endif%} |
| //NODE PROPERTIES |
| //environment_variables is a HashMap of key/value pairs |
| environment_variables: [ {% for env in docker.environment_variables %} {% for name, value in env.items() %} |
| '{{name}}': '{{value}}', |
| {% endfor %}{% endfor %} ], |
| //tool location key/value pairs from https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/tools/ToolLocationNodeProperty.java |
| //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. |
| //For example let's say you have a global tool configuration named OracleJDK8 for JDK installations |
| //tool_locations would be something like ['hudson.model.JDK$DescriptorImpl@OracleJDK8': '/path/to/java_home'] |
| //If you're unsure of the tool@name then check config.xml where YADocker configurations are saved. |
| tool_locations: '{{docker.tool_locations}}', |
| remote_fs_root_mapping: '{{docker.remote_fs_root_mapping}}' |
| |
| ], |
| {% endfor %} |
| ], |
| ], |
| {% endfor %} |
| ] as JSONArray |
| |
| //detect an existing global tool |
| def detectGlobalToolExists(String location) { |
| def (toolType, toolName) = location.split('@') |
| boolean found_installation = Jenkins.instance.getExtensionList(toolType)[0].installations.findAll { it.name == toolName } as boolean |
| return found_installation |
| } |
| |
| //return a launcher |
| def selectLauncher(String launcherType, JSONObject obj) { |
| switch(launcherType) { |
| case 'launch_ssh': |
| def jenkinsCredentials = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials( |
| com.cloudbees.plugins.credentials.Credentials.class, |
| Jenkins.instance, |
| null, |
| null |
| ); |
| def launch_credentials = null |
| for (creds in jenkinsCredentials) { |
| if(creds.id == obj.optString('launch_ssh_credentials_id')){ |
| launch_credentials = creds; |
| } |
| } |
| DockerSSHConnector sshConnector = new DockerSSHConnector(obj.optInt('launch_ssh_port', 22), |
| launch_credentials, |
| obj.optString('launch_ssh_credentials_id'), |
| obj.optString('launch_ssh_jvm_options'), |
| obj.optString('launch_ssh_java_path'), |
| null, |
| obj.optString('launch_ssh_prefix_start_slave_command'), |
| obj.optString('launch_ssh_suffix_start_slave_command'), |
| obj.optInt('launch_ssh_connection_timeout'), |
| obj.optInt('launch_ssh_max_num_retries'), |
| obj.optInt('launch_ssh_time_wait_between_retries'), |
| new NonVerifyingKeyVerificationStrategy() |
| ) |
| return new DockerComputerSSHLauncher(sshConnector) |
| case 'launch_jnlp': |
| DockerComputerJNLPLauncher dockerComputerJNLPLauncher = new DockerComputerJNLPLauncher() |
| dockerComputerJNLPLauncher.setUser(obj.optString('launch_jnlp_linux_user','jenkins')) |
| dockerComputerJNLPLauncher.setLaunchTimeout(obj.optLong('launch_jnlp_lauch_timeout', 120L)) |
| dockerComputerJNLPLauncher.setSlaveOpts(obj.optString('launch_jnlp_slave_jar_options')) |
| dockerComputerJNLPLauncher.setJvmOpts(obj.optString('launch_jnlp_slave_jvm_options')) |
| dockerComputerJNLPLauncher.setJenkinsUrl(obj.optString('launch_jnlp_different_jenkins_master_url')) |
| dockerComputerJNLPLauncher.setNoCertificateCheck(obj.optBoolean('launch_jnlp_ignore_certificate_check', false)) |
| return dockerComputerJNLPLauncher |
| default: |
| return null |
| } |
| } |
| |
| //create a new instance of the DockerCloud class from a JSONObject |
| def newDockerCloud(JSONObject obj) { |
| DockerConnector connector = new DockerConnector(obj.optString('docker_url', 'tcp://localhost:2375')) |
| if(obj.optInt('connect_timeout')) { |
| connector.setConnectTimeout(obj.optInt('connect_timeout')) |
| } |
| connector.setApiVersion(obj.optString('docker_api_version')) |
| connector.setCredentialsId(obj.optString('host_credentials_id')) |
| //select connection_type |
| List<String> connection_types = ['NETTY', 'JERSEY'] |
| String connection_type_default = 'JERSEY' |
| String user_selected_connection_type = obj.optString('connection_type', connection_type_default).toUpperCase() |
| String connection_type = (user_selected_connection_type in connection_types)? user_selected_connection_type : connection_type_default |
| connector.setConnectorType(ConnectorType."${connection_type}") |
| |
| DockerCloud cloud = new DockerCloud(obj.optString('cloud_name'), |
| bindJSONToList(DockerSlaveTemplate.class, obj.opt('docker_templates')), |
| obj.optInt('max_containers', 1), |
| connector) |
| |
| return cloud |
| } |
| |
| def newDockerSlaveTemplate(JSONObject obj) { |
| //DockerPullImage |
| DockerPullImage pullImage = new DockerPullImage() |
| String pullStrategy = obj.optString('pull_strategy', 'PULL_LATEST').toUpperCase() |
| if(pullStrategy in ['PULL_LATEST', 'PULL_ALWAYS', 'PULL_ONCE', 'PULL_NEVER']) { |
| pullImage.setPullStrategy(DockerImagePullStrategy."${pullStrategy}") |
| } else { |
| pullImage.setPullStrategy(DockerImagePullStrategy.PULL_LATEST) |
| } |
| pullImage.setCredentialsId(obj.optString('pull_registry_credentials_id')) |
| |
| //DockerCreateContainer |
| DockerCreateContainer createContainer = new DockerCreateContainer() |
| createContainer.setBindPorts(obj.optString('port_bindings')) |
| createContainer.setBindAllPorts(obj.optBoolean('bind_all_declared_ports', false)) |
| createContainer.setDnsString(obj.optString('dns')) |
| createContainer.setHostname(obj.optString('hostname')) |
| if(obj.optLong('memory_limit_in_mb')) { |
| createContainer.setMemoryLimit(obj.optLong('memory_limit_in_mb')) |
| } |
| createContainer.setPrivileged(obj.optBoolean('run_container_privileged', true)) |
| createContainer.setTty(obj.optBoolean('allocate_pseudo_tty', false)) |
| if(obj.optJSONArray('volumes')) { |
| createContainer.setVolumes(obj.optJSONArray('volumes') as List<String>) |
| } else { |
| createContainer.setVolumesString(obj.optString('volumes')) |
| } |
| if(obj.optJSONArray('volumes_from')) { |
| createContainer.setVolumesFrom(obj.optJSONArray('volumes_from') as List<String>) |
| } else { |
| createContainer.setVolumesFromString(obj.optString('volumes_from')) |
| } |
| createContainer.setMacAddress(obj.optString('mac_address')) |
| if(obj.optInt('cpu_shares')) { |
| createContainer.setCpuShares(obj.optInt('cpu_shares')) |
| } |
| createContainer.setCommand(obj.optString('docker_command')) |
| if(obj.optJSONArray('environment')) { |
| createContainer.setEnvironment(obj.optJSONArray('environment') as List<String>) |
| } else { |
| createContainer.setEnvironmentString(obj.optString('environment')) |
| } |
| if(obj.optJSONArray('extra_hosts')) { |
| createContainer.setExtraHosts(obj.optJSONArray('extra_hosts') as List<String>) |
| } else { |
| createContainer.setExtraHostsString(obj.optString('extra_hosts')) |
| } |
| createContainer.setNetworkMode(obj.optString('network_mode')) |
| if(obj.optJSONArray('devices')) { |
| createContainer.setDevices(obj.optJSONArray('devices')) |
| } else { |
| createContainer.setDevicesString(obj.optString('devices')) |
| } |
| createContainer.setCpusetCpus(obj.optString('cpuset_constraint_cpus')) |
| createContainer.setCpusetMems(obj.optString('cpuset_constraint_mems')) |
| if(obj.optJSONArray('links')) { |
| createContainer.setLinks(obj.optJSONArray('links')) |
| } else { |
| createContainer.setLinksString(obj.optString('links')) |
| } |
| |
| //DockerStopContainer |
| DockerStopContainer stopContainer = new DockerStopContainer() |
| stopContainer.setTimeout(obj.optInt('stop_container_timeout', 10)) |
| |
| //DockerRemoveContainer |
| DockerRemoveContainer removeContainer = new DockerRemoveContainer() |
| removeContainer.setRemoveVolumes(obj.optBoolean('remove_volumes', false)) |
| removeContainer.setForce(obj.optBoolean('force_remove_containers', false)) |
| |
| //DockerContainerLifecycle |
| DockerContainerLifecycle dockerContainerLifecycle = new DockerContainerLifecycle() |
| dockerContainerLifecycle.setImage(obj.optString('docker_image_name')) |
| dockerContainerLifecycle.setPullImage(pullImage) |
| dockerContainerLifecycle.setCreateContainer(createContainer) |
| dockerContainerLifecycle.setStopContainer(stopContainer) |
| dockerContainerLifecycle.setRemoveContainer(removeContainer) |
| |
| //DockerOnceRetentionStrategy |
| //this availability_strategy is for "run_once". We can customize it later |
| RetentionStrategy retentionStrategy = new DockerOnceRetentionStrategy(obj.optInt('availability_idle_timeout', 10)) |
| |
| //DockerComputerLauncher |
| //select a launch method from the list of available launch methods |
| List<String> launch_methods = ['launch_ssh', 'launch_jnlp'] |
| String default_launch_method = 'launch_jnlp' |
| String user_selected_launch_method = obj.optString('launch_method', default_launch_method).toLowerCase() |
| String launch_method = (user_selected_launch_method in launch_methods)? user_selected_launch_method : default_launch_method |
| DockerComputerLauncher launcher = selectLauncher(launch_method, obj) |
| |
| //DockerSlaveTemplate |
| DockerSlaveTemplate dockerSlaveTemplate = new DockerSlaveTemplate() |
| dockerSlaveTemplate.setDockerContainerLifecycle(dockerContainerLifecycle) |
| dockerSlaveTemplate.setLabelString(obj.optString('labels', 'docker')) |
| String node_usage = (obj.optString('usage', 'EXCLUSIVE').toUpperCase().equals('NORMAL'))? 'NORMAL' : 'EXCLUSIVE' |
| dockerSlaveTemplate.setMode(Node.Mode."${node_usage}") |
| dockerSlaveTemplate.setNumExecutors(obj.optInt('executors', 1)) |
| dockerSlaveTemplate.setRetentionStrategy(retentionStrategy) |
| dockerSlaveTemplate.setLauncher(launcher) |
| dockerSlaveTemplate.setRemoteFs(obj.optString('remote_fs_root', '/srv/jenkins')) |
| dockerSlaveTemplate.setMaxCapacity(obj.optInt('max_instances', 1)) |
| //dockerSlaveTemplate.setRemoteFsMapping(obj.optString('remote_fs_root_mapping')) |
| //dockerSlaveTemplate.remoteFsMapping = obj.optString('remote_fs_root_mapping') |
| //define NODE PROPERTIES |
| List<NodeProperty> nodeProperties = [] as List<NodeProperty> |
| if(obj.optJSONObject('environment_variables')) { |
| HashMap<String,String> env = obj.optJSONObject('environment_variables') as HashMap<String,String> |
| List<EnvironmentVariablesNodeProperty.Entry> envEntries = [] as List<EnvironmentVariablesNodeProperty.Entry> |
| (env.keySet() as String[]).each { var -> |
| envEntries << (new EnvironmentVariablesNodeProperty.Entry(var, env[var])) |
| } |
| //add environment_variables to nodeProperties |
| nodeProperties << (new EnvironmentVariablesNodeProperty(envEntries)) |
| } |
| if(obj.optJSONObject('tool_locations')) { |
| HashMap<String,String> tool = obj.optJSONObject('tool_locations') as HashMap<String,String> |
| List<ToolLocationNodeProperty.ToolLocation> toolLocations = [] as List<ToolLocationNodeProperty.ToolLocation> |
| (tool.keySet() as String[]).each { location -> |
| //tool location is only valid if it contains a type i.e. @ symbol |
| if(location.contains('@') && detectGlobalToolExists(location)) { |
| toolLocations << (new ToolLocationNodeProperty.ToolLocation(location, tool[location])) |
| } else { |
| //alert the user they configured an invalid tool type or name in tool_locations |
| println "WARNING: Invalid tool: '${location}'. Format should be 'type@name' where both type and name already exist in global tool configurations." |
| } |
| } |
| nodeProperties << (new ToolLocationNodeProperty(toolLocations)) |
| } |
| //set NODE PROPERTIES |
| if(nodeProperties) { |
| dockerSlaveTemplate.setNodeProperties(nodeProperties) |
| } |
| return dockerSlaveTemplate |
| } |
| |
| def bindJSONToList(Class type, Object src) { |
| if(!(type == DockerCloud) && !(type == DockerSlaveTemplate)) { |
| throw new Exception("Must use DockerCloud or DockerSlaveTemplate class.") |
| } |
| //docker_array should be a DockerCloud or DockerSlaveTemplate |
| ArrayList<?> docker_array |
| if(type == DockerCloud){ |
| docker_array = new ArrayList<DockerCloud>() |
| } else { |
| docker_array = new ArrayList<DockerSlaveTemplate>() |
| } |
| //cast the configuration object to a Docker instance which Jenkins will use in configuration |
| if (src instanceof JSONObject) { |
| //uses string interpolation to call a method |
| //e.g instead of newDockerCloud(src) we use instead... |
| docker_array.add("new${type.getSimpleName()}"(src)) |
| } else if(src instanceof JSONArray) { |
| for (Object o : src) { |
| if (o instanceof JSONObject) { |
| docker_array.add("new${type.getSimpleName()}"(o)) |
| } |
| } |
| } |
| return docker_array |
| } |
| |
| if(!Jenkins.instance.isQuietingDown()) { |
| ArrayList<DockerCloud> clouds = new ArrayList<DockerCloud>() |
| clouds = bindJSONToList(DockerCloud.class, clouds_yadocker) |
| if(clouds.size() > 0) { |
| {%if dryrun == True %} |
| clouds*.name.each { cloudName -> |
| println "DRYRUN: Configured Yet Another Docker cloud ${cloudName}" |
| } |
| {%else%} |
| dockerConfigUpdated = true |
| Jenkins.instance.clouds.removeAll(DockerCloud) |
| Jenkins.instance.clouds.addAll(clouds) |
| clouds*.name.each { cloudName -> |
| println "Configured Yet Another Docker cloud ${cloudName}" |
| } |
| Jenkins.instance.save() |
| {%endif%} |
| } else { |
| println 'Nothing changed. No Yet Another docker clouds to configure.' |
| } |
| } else { |
| println 'Shutdown mode enabled. Configure Yet Another Docker clouds SKIPPED.' |
| } |
| |
| //return null so there's no result value in the script console |
| null |
| |
| /* |
| The MIT License (MIT) |
| |
| Copyright 2017 Sam Gleske - https://github.com/samrocketman/jenkins-bootstrap-jervis |
| |
| Permission is hereby granted, free of charge, to any person obtaining a copy |
| of this software and associated documentation files (the "Software"), to |
| deal in the Software without restriction, including without limitation the |
| rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
| sell copies of the Software, and to permit persons to whom the Software is |
| furnished to do so, subject to the following conditions: |
| |
| The above copyright notice and this permission notice shall be included in |
| all copies or substantial portions of the Software. |
| |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| IN THE SOFTWARE. |
| */ |