Init push for ci-yadp

Signed-off-by: Benjamin Copeland <ben.copeland@linaro.org>
Change-Id: Iafb9217fc347da3d29f9fbdf3dcf89383cbf30bb
diff --git a/docker_ssh_buildslave.yml b/docker_ssh_buildslave.yml
new file mode 100644
index 0000000..3a3a2b7
--- /dev/null
+++ b/docker_ssh_buildslave.yml
@@ -0,0 +1,7 @@
+---
+# Name: buildslave SSH user creds on jenkins
+launch_ssh_credentials_id: 'buildslave'
+launch_ssh_port: '22'
+launch_ssh_connection_timeout: '60'
+launch_ssh_max_num_retries: '1000'
+launch_ssh_time_wait_between_retries: '60'
diff --git a/docker_templates_amd64.yml b/docker_templates_amd64.yml
new file mode 100644
index 0000000..1abc993
--- /dev/null
+++ b/docker_templates_amd64.yml
@@ -0,0 +1,9 @@
+---
+- docker-ros-builder:
+  docker_image_name: 'trustedfirmware/ci-amd64-ubuntu:bionic'
+  labels: 'docker-amd64-bionic'
+  launch_method: 'launch_ssh'
+  ssh: !include docker_ssh_buildslave.yml
+  environment:
+  - 'PATH=/home/buildslave/tools/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
+  - 'PLANTUML_JAR_PATH=/usr/share/plantuml/plantuml.jar'
diff --git a/hosts b/hosts
new file mode 100644
index 0000000..4684fc5
--- /dev/null
+++ b/hosts
@@ -0,0 +1,10 @@
+---
+# List of YADP Hosts
+
+- x86_64-TF-01:
+  cloud_name: x86_64-TF-01
+  docker_url: tcp://148.251.195.69:2375
+  host_credentials_id: docker-access
+  max_containers: 50 # equals the amount of nodes we have in the swarm
+  docker_templates: !include docker_templates_amd64.yml
+
diff --git a/templates/configure-yadocker-cloud.groovy.j2 b/templates/configure-yadocker-cloud.groovy.j2
new file mode 100644
index 0000000..df444e9
--- /dev/null
+++ b/templates/configure-yadocker-cloud.groovy.j2
@@ -0,0 +1,435 @@
+/*
+   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}}',
+         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: '{{docker.environment_variables}}',
+         //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.
+ */
diff --git a/yadp_builder.py b/yadp_builder.py
new file mode 100755
index 0000000..8e9bf06
--- /dev/null
+++ b/yadp_builder.py
@@ -0,0 +1,121 @@
+import argparse
+import yaml
+import os
+from jinja2 import FileSystemLoader, Environment
+import jenkins
+import logging
+
+
+class LoaderMeta(type):
+
+    def __new__(metacls, __name__, __bases__, __dict__):
+        """Add include constructer to class."""
+
+        # register the include constructor on the class
+        cls = super().__new__(metacls, __name__, __bases__, __dict__)
+        cls.add_constructor('!include', cls.construct_include)
+
+        return cls
+
+
+class Loader(yaml.Loader, metaclass=LoaderMeta):
+    """YAML Loader with `!include` constructor."""
+
+    def __init__(self, stream):
+        """Initialise Loader."""
+
+        try:
+            self._root = os.path.split(stream.name)[0]
+        except AttributeError:
+            self._root = os.path.curdir
+
+        super().__init__(stream)
+
+    def construct_include(self, node):
+        if isinstance(node, yaml.ScalarNode):
+            return self.extractFile(self.construct_scalar(node))
+
+        elif isinstance(node, yaml.SequenceNode):
+            result = []
+            for filename in self.construct_sequence(node):
+                result += self.extractFile(filename)
+            return result
+
+        elif isinstance(node, yaml.MappingNode):
+            result = {}
+            for k,v in self.construct_mapping(node).iteritems():
+                result[k] = self.extractFile(v)
+            return result
+
+        else:
+            print "Error:: unrecognised node type in !include statement"
+            raise yaml.constructor.ConstructorError
+
+
+    def extractFile(self, filename):
+        filepath = os.path.join(self._root, filename)
+        with open(filepath, 'r') as f:
+            return yaml.load(f, Loader=Loader)
+
+
+def jinja2_from_template(directory, template_name, data, dryrun=False):
+    loader = FileSystemLoader(directory)
+    env = Environment(loader=loader)
+    template = env.get_template(template_name)
+    return template.render(hosts=data, dryrun=dryrun)
+
+
+def get_parser():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-u', '--username', type=str,
+                        default=os.environ.get('JJB_USER'),
+                        help='Username for Jenkins server')
+    parser.add_argument('-p', '--password', type=str,
+                        default=os.environ.get('JJB_PASSWORD'),
+                        help='Password for Jenkins server')
+    parser.add_argument('-s', '--server', type=str,
+                        default='http://localhost:8080',
+                        help='Jenkins server URL. e.g. http://localhost:8080')
+    parser.add_argument('-i', '--inventory', type=str, default='hosts',
+                        help='specify inventory host path')
+    parser.add_argument('-l', '--loglevel', default='INFO',
+                        help="Setting logging level, default: %(default)s")
+    parser.add_argument('--dryrun', action='store_true',
+                        help='Do not publish to Jenkins')
+    parser.add_argument('--local', action='store_true',
+                        help='Create tmp file only, to be used with dryrun.')
+    return parser
+
+
+if __name__ == '__main__':
+    parser = get_parser()
+    args = parser.parse_args()
+    logging.basicConfig(level=args.loglevel)
+    with open(args.inventory, 'r') as f:
+        data = yaml.load(f, Loader=Loader)
+    logging.debug(data)
+    template_output = jinja2_from_template(
+                      './templates',
+                      'configure-yadocker-cloud.groovy.j2', data)
+
+    if not args.local:
+        server = jenkins.Jenkins(args.server, username=args.username,
+                             password=args.password)
+    if args.dryrun:
+        with open('/tmp/configure-yadocker-cloud.groovy', 'w') as fw:
+            fw.write(template_output)
+        template_output = jinja2_from_template(
+                          './templates',
+                          'configure-yadocker-cloud.groovy.j2', data, args.dryrun)
+        if not args.local:
+            publishdry = server.run_script(template_output)
+            if 'error' in publishdry:
+                logging.info(publishdry)
+                exit(1)
+            logging.info(publishdry)
+        else:
+            logging.info('Template file created at \
+                    /tmp/configure-yadocker-cloud.groovy')
+    else:
+        publish = server.run_script(template_output)
+        logging.info(publish)