├── LICENSE ├── README.md ├── defaults └── main.yml ├── files ├── javaposse.jobdsl.plugin.GlobalJobDslSecurityConfiguration.xml ├── jenkins.CLI.xml ├── jenkins.model.DownloadSettings.xml └── jenkins.security.QueueItemAuthenticatorConfiguration.xml ├── handlers └── main.yml ├── tasks ├── main.yml └── seed-job.yml └── templates ├── admin-config.xml.j2 ├── config.xml.j2 ├── credentials.xml.j2 ├── jenkins.model.JenkinsLocationConfiguration.xml.j2 ├── jobs.groovy.j2 ├── scriptApproval.xml.j2 └── seed-config.xml.j2 /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Joel Wilsson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible role: Jenkins with Pipeline 2 | 3 | Installs a minimal installation of Jenkins with the Pipeline plugin to create jobs. 4 | Assumes there will be one job for each git repository, and that the pipelines 5 | are specified in `Jenkinsfile` at the root of each repository. 6 | 7 | The `jenkins` user must be able to clone from the git host, so the role also 8 | install SSH keys. 9 | 10 | ```yaml 11 | - name: jenkins-pipeline 12 | jenkins_admin_password: use-a-vault-variable-for-this 13 | jenkins_ssh_private_key: jenkins-id_rsa 14 | jenkins_ssh_public_key: jenkins-id_rsa.pub 15 | jenkins_git_user: git 16 | jenkins_git_host: git.example.com 17 | jenkins_git_path: git 18 | jenkins_git_repositories: 19 | - your-repo 20 | ``` 21 | 22 | See [this blog post](https://wjoel.com/posts/ansible-jenkins-pipeline-part-1.html) 23 | for more information. 24 | 25 | ## SSH keys 26 | 27 | You may need to add SSH keys to `known_hosts` for Jenkins to be able 28 | to check out git repositories. This can also be done with Ansible. 29 | Here's an example: 30 | 31 | ```yaml 32 | tasks: 33 | - name: Add git.example.com to known hosts 34 | become: yes 35 | known_hosts: 36 | path: '~jenkins/.ssh/known_hosts' 37 | name: git.example.com 38 | key: "git.example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKpPLDi490gas7v6DWscdzSoWOOZEAa70riTVVQlD/FXwjMA8ZX4q/InySgsEIseqvXqKwpQ+AncSnTNHIKy4Q6kT1n0tFmqyJTbBsfx26ljqZ0KkEm1PtHaNtXYpEfq1LHK8a813/HDiBpdJtbU7R5pCqFE+vd52gZyUG0WRA9CcZSmWj1PdiwfLIMx8CI0arsYbh02/pPeQ8NIGNgZ7H7cDtjqFp0bOYkO8KVJZajpr7YsYI6CBnSuAwaCicdYvAbY0EgGkHyNRFJWDLOrHyHsUOntxm7a6jFTkIxZ3Sri0R9EcpJV9/WHhW4GRm5SfqW7uHzU9qJ2/8a2s+AjP13Hv5H08iXefKQ8BzuB9qMxbC2uRq3WHAU6/T5H4/N7BTsqTE/cTEfZspUG9JeRBQMS3GQt6TCEc7EbZ1NzZjSBduwbJFop80TufED7sVT3LhkbzRDa45a45n1Q8N8vsnIrEWdCAaKwcdP1lIfzwWKoNzwznEJLNZdQr1lKa2R/nrUYLY0JUjwJyMgMYgxQriMccUqYzWBeE1McQkXFqec3Xda1hyMgA7lIe8sF5iMxZmed91GeddcUh6D0WJkkO4iI58OESlAJ28X9sVsWV/3W2kbmjeFnf/QZn6Sgc0QWFrZT4vMEMjJtWvL9itmIrON2fvAPKJIhhG6deNRfuPjw==" 39 | - name: Add git.example.com (ECDSA) to known hosts 40 | become: yes 41 | known_hosts: 42 | path: '~jenkins/.ssh/known_hosts' 43 | name: git.example.com 44 | key: "git.example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGKIHhtR206LKUvevpSuL5nOt9LBzNVXkqRnBdBqhaXbuStPM2OXQQRUxgA3PBb05lhtbMXol7di1Qp75BDdJM4=" 45 | - name: Set permissions for known_hosts 46 | become: yes 47 | file: path="~jenkins/.ssh/known_hosts" owner=jenkins group=jenkins mode=0600 48 | ``` 49 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | jenkins_java_args: -Djava.awt.headless=true -Djenkins.install.runSetupWizard=false 3 | jenkins_java_extra_args: "" 4 | jenkins_defaults_file: /etc/default/jenkins 5 | jenkins_plugins: 6 | - git 7 | - job-dsl 8 | - workflow-aggregator 9 | - workflow-cps 10 | jenkins_seed_name: pipeline-seed 11 | jenkins_seed_template: seed-config.xml.j2 12 | jenkins_job_template: jobs.groovy.j2 13 | jenkins_version: "2.107.1" 14 | jenkins_approved_signatures: 15 | - method hudson.plugins.git.GitSCM getBranches 16 | - method hudson.plugins.git.GitSCM getUserRemoteConfigs 17 | - method hudson.plugins.git.GitSCMBackwardCompatibility getExtensions 18 | jenkins_acl_approved_signatures: [] 19 | jenkins_slave_agent_port: 0 20 | jenkins_cloud_configs: "" 21 | jenkins_admin_address: nobody@example.com 22 | #jenkins_url: 23 | -------------------------------------------------------------------------------- /files/javaposse.jobdsl.plugin.GlobalJobDslSecurityConfiguration.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | -------------------------------------------------------------------------------- /files/jenkins.CLI.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | 5 | -------------------------------------------------------------------------------- /files/jenkins.model.DownloadSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | 5 | -------------------------------------------------------------------------------- /files/jenkins.security.QueueItemAuthenticatorConfiguration.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart Jenkins 3 | service: name=jenkins state=restarted 4 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install ca-certificates 3 | apt: name=ca-certificates state=present 4 | 5 | - name: Add Jenkins repository key 6 | apt_key: id=D50582E6 url=https://pkg.jenkins.io/debian/jenkins.io.key 7 | 8 | - name: Add Jenkins repository 9 | apt_repository: repo='deb http://pkg.jenkins.io/debian-stable binary/' state=present update_cache=yes 10 | 11 | - name: Install Jenkins 12 | apt: name="jenkins={{ jenkins_version }}" state=present update_cache=yes 13 | 14 | - name: Set Jenkins JAVA_ARGS 15 | lineinfile: 16 | dest: "{{ jenkins_defaults_file }}" 17 | insertbefore: "^JENKINS_ARGS.*" 18 | line: "JAVA_ARGS=\"{{ jenkins_java_args }} {{ jenkins_java_extra_args }}\"" 19 | register: jenkins_defaults 20 | 21 | - name: Allow logins with Jenkins SSH key 22 | authorized_key: user=jenkins key="{{ lookup('file', jenkins_ssh_public_key) }}" 23 | when: "jenkins_ssh_public_key is defined and jenkins_ssh_public_key is not none" 24 | 25 | - name: Ensure Jenkins SSH directory exists 26 | file: path="~jenkins/.ssh" owner=jenkins group=jenkins mode=0755 state=directory 27 | 28 | - name: Install Jenkins SSH key 29 | copy: src="{{ jenkins_ssh_private_key }}" dest="~jenkins/.ssh/id_rsa" owner=jenkins group=jenkins mode=0600 30 | when: "jenkins_ssh_private_key is defined and jenkins_ssh_private_key is not none" 31 | 32 | - name: Remove initial Jenkins password 33 | file: name=/var/lib/jenkins/secrets/initialAdminPassword state=absent 34 | 35 | - name: Create Jenkins admin password hash 36 | shell: echo -n "{{ jenkins_admin_password }}{ansible_jenkins}" | sha256sum - | awk '{ print $1; }' 37 | register: jenkins_password_hash 38 | 39 | - name: Create admin user directory 40 | file: path="~jenkins/users/admin" owner=jenkins group=jenkins mode=0755 state=directory recurse=yes 41 | 42 | - name: Create admin 43 | template: src=admin-config.xml.j2 dest="~jenkins/users/admin/config.xml" force=no 44 | register: jenkins_admin_config 45 | 46 | - name: Create config 47 | template: src=config.xml.j2 dest="~jenkins/config.xml" 48 | register: jenkins_config_change 49 | 50 | - name: Create scriptApproval 51 | template: src=scriptApproval.xml.j2 dest="~jenkins/scriptApproval.xml" 52 | register: jenkins_config_change 53 | 54 | - name: Create Jenkins location configuration 55 | template: 56 | src: jenkins.model.JenkinsLocationConfiguration.xml.j2 57 | dest: "~jenkins/jenkins.model.JenkinsLocationConfiguration.xml" 58 | when: jenkins_url is defined and jenkins_url != "" 59 | register: jenkins_config_change 60 | 61 | - name: Create CLI config (disable CLI) 62 | copy: src=jenkins.CLI.xml dest="~jenkins/jenkins.CLI.xml" 63 | 64 | - name: Create queue item authenticator configuration 65 | copy: 66 | src: jenkins.security.QueueItemAuthenticatorConfiguration.xml 67 | dest: "~jenkins/jenkins.security.QueueItemAuthenticatorConfiguration.xml" 68 | 69 | - name: Create job-dsl security configuration 70 | copy: 71 | src: javaposse.jobdsl.plugin.GlobalJobDslSecurityConfiguration.xml 72 | dest: "~jenkins/javaposse.jobdsl.plugin.GlobalJobDslSecurityConfiguration.xml" 73 | 74 | - name: Create credentials configuration 75 | template: 76 | src: credentials.xml.j2 77 | dest: "~jenkins/credentials.xml" 78 | 79 | - name: Create download settings configuration 80 | copy: 81 | src: jenkins.model.DownloadSettings.xml 82 | dest: "~jenkins/jenkins.model.DownloadSettings.xml" 83 | 84 | - name: Create /var/lib/jenkins/secrets 85 | file: path="/var/lib/jenkins/secrets" owner=jenkins group=jenkins mode=0700 state=directory recurse=yes 86 | 87 | - name: Enable master to slave access control 88 | copy: 89 | content: "false" 90 | dest: "/var/lib/jenkins/secrets/slave-to-master-security-kill-switch" 91 | 92 | # Restart with a task instead of a handler, since we need those changes to 93 | # be applied right away so that we can use the admin password in API calls. 94 | - name: Restart Jenkins if necessary 95 | service: name=jenkins state=restarted 96 | when: jenkins_defaults|changed or jenkins_admin_config|changed or jenkins_config|changed 97 | 98 | - name: Wait for Jenkins to become available 99 | wait_for: port=8080 100 | 101 | - name: Get Jenkins crumb 102 | uri: 103 | user: admin 104 | password: "{{ jenkins_admin_password }}" 105 | force_basic_auth: yes 106 | url: "http://127.0.0.1:8080/crumbIssuer/api/json" 107 | return_content: yes 108 | status_code: 200, 404 109 | register: jenkins_crumb 110 | until: jenkins_crumb.status == 200 and jenkins_crumb.content.find('Please wait while Jenkins is getting ready to work') == -1 111 | retries: 10 112 | delay: 5 113 | 114 | - name: Set crumb token 115 | set_fact: 116 | jenkins_crumb_token: "{{ jenkins_crumb.json.crumbRequestField }}={{ jenkins_crumb.json.crumb }}" 117 | 118 | - name: Get installed plugins 119 | uri: 120 | user: admin 121 | password: "{{ jenkins_admin_password }}" 122 | force_basic_auth: yes 123 | url: "http://127.0.0.1:8080/pluginManager/api/json?tree=plugins[shortName]&{{ jenkins_crumb_token }}" 124 | return_content: yes 125 | register: jenkins_installed_plugins 126 | 127 | - name: Install plugins 128 | uri: 129 | user: admin 130 | password: "{{ jenkins_admin_password }}" 131 | force_basic_auth: yes 132 | url: "http://127.0.0.1:8080/pluginManager/install?plugin.{{ item }}.default=on&{{ jenkins_crumb_token }}" 133 | method: POST 134 | status_code: [200, 302] 135 | when: item not in jenkins_installed_plugins.json.plugins|map(attribute='shortName')|list 136 | with_items: "{{ jenkins_plugins }}" 137 | 138 | - name: Wait for plugins to be installed 139 | uri: 140 | user: admin 141 | password: "{{ jenkins_admin_password }}" 142 | force_basic_auth: yes 143 | url: "http://127.0.0.1:8080/updateCenter/installStatus?{{ jenkins_crumb_token }}" 144 | return_content: yes 145 | register: jenkins_plugin_status 146 | until: "'Pending' not in jenkins_plugin_status.json.data.jobs|map(attribute='installStatus')" 147 | retries: 60 148 | delay: 10 149 | 150 | - name: Add cloud configs to config.xml 151 | replace: 152 | dest: "~jenkins/config.xml" 153 | regexp: "" 154 | replace: | 155 | 156 | {{ jenkins_cloud_configs }} 157 | 158 | 159 | - name: Check if we need to restart Jenkins to activate plugins 160 | uri: 161 | user: admin 162 | password: "{{ jenkins_admin_password }}" 163 | force_basic_auth: yes 164 | url: "http://127.0.0.1:8080/updateCenter/api/json\ 165 | ?tree=restartRequiredForCompletion&{{ jenkins_crumb_token }}" 166 | return_content: yes 167 | register: jenkins_restart_required 168 | 169 | - name: Restart Jenkins to activate new plugins 170 | service: name=jenkins state=restarted 171 | when: jenkins_restart_required.json.restartRequiredForCompletion|bool 172 | 173 | - name: Wait for Jenkins to become available 174 | wait_for: port=8080 175 | 176 | - include_tasks: seed-job.yml 177 | -------------------------------------------------------------------------------- /tasks/seed-job.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get list of jobs 3 | uri: 4 | user: admin 5 | password: "{{ jenkins_admin_password }}" 6 | force_basic_auth: yes 7 | url: "http://127.0.0.1:8080/api/json?tree=jobs[name]" 8 | return_content: yes 9 | register: jenkins_jobs 10 | until: jenkins_jobs.content.find('Please wait while Jenkins is getting ready to work') == -1 11 | retries: 10 12 | delay: 5 13 | 14 | - name: Check if seed job exists 15 | set_fact: 16 | jenkins_seed_job_exists: "{{ jenkins_seed_name in (jenkins_jobs.content|from_json).jobs|map(attribute='name')|list }}" 17 | 18 | - name: Create seed job 19 | uri: 20 | user: admin 21 | password: "{{ jenkins_admin_password }}" 22 | force_basic_auth: yes 23 | url: "http://127.0.0.1:8080/createItem?name={{ jenkins_seed_name }}&{{ jenkins_crumb_token }}" 24 | method: POST 25 | headers: 26 | Content-Type: "application/xml" 27 | body: "{{ lookup('template', jenkins_seed_template) }}" 28 | register: jenkins_seed_job_created 29 | when: not jenkins_seed_job_exists 30 | 31 | - name: Update seed job 32 | uri: 33 | user: admin 34 | password: "{{ jenkins_admin_password }}" 35 | force_basic_auth: yes 36 | url: "http://127.0.0.1:8080/job/{{ jenkins_seed_name }}/config.xml?{{ jenkins_crumb_token }}" 37 | method: POST 38 | headers: 39 | Content-Type: "application/xml" 40 | body: "{{ lookup('template', jenkins_seed_template) }}" 41 | register: jenkins_seed_job_updated 42 | when: jenkins_seed_job_exists 43 | 44 | - name: Run seed job 45 | uri: 46 | user: admin 47 | password: "{{ jenkins_admin_password }}" 48 | force_basic_auth: yes 49 | url: "http://127.0.0.1:8080/job/{{ jenkins_seed_name }}/build?{{ jenkins_crumb_token }}" 50 | method: POST 51 | status_code: 201 52 | register: jenkins_seed_job_started 53 | when: jenkins_seed_job_created|success or jenkins_seed_job_updated|success 54 | 55 | - name: Wait for seed job 56 | uri: 57 | user: admin 58 | password: "{{ jenkins_admin_password }}" 59 | force_basic_auth: yes 60 | url: "http://127.0.0.1:8080/job/{{ jenkins_seed_name }}/lastBuild/buildNumber?{{ jenkins_crumb_token }}" 61 | method: GET 62 | status_code: 200 63 | register: jenkins_seed 64 | until: jenkins_seed['status']|default(0) == 200 65 | retries: 10 66 | delay: 5 67 | -------------------------------------------------------------------------------- /templates/admin-config.xml.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | admin 4 | 5 | 6 | 7 | 8 | 9 | All 10 | false 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | false 21 | 22 | 23 | ansible_jenkins:{{ jenkins_password_hash.stdout }} 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /templates/config.xml.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.0 5 | 2 6 | NORMAL 7 | true 8 | 9 | true 10 | 11 | 12 | true 13 | false 14 | 15 | false 16 | 17 | ${ITEM_ROOTDIR}/workspace 18 | ${ITEM_ROOTDIR}/builds 19 | 20 | 21 | 22 | 23 | 24 | 0 25 | 26 | 27 | 28 | All 29 | false 30 | false 31 | 32 | 33 | 34 | All 35 | {{ jenkins_slave_agent_port }} 36 | 37 | JNLP-connect 38 | JNLP2-connect 39 | JNLP3-connect 40 | 41 | 42 | 43 | false 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /templates/credentials.xml.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | GLOBAL 11 | {{ jenkins_git_user }} 12 | 13 | {{ jenkins_git_user }} 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /templates/jenkins.model.JenkinsLocationConfiguration.xml.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ jenkins_admin_address }} 4 | {{ jenkins_url }} 5 | 6 | -------------------------------------------------------------------------------- /templates/jobs.groovy.j2: -------------------------------------------------------------------------------- 1 | {% for repository in jenkins_git_repositories %} 2 | pipelineJob('{{ repository }}') { 3 | triggers { 4 | scm '' 5 | } 6 | definition { 7 | cpsScm { 8 | scm { 9 | git { 10 | remote { 11 | url('{{ jenkins_git_user }}@{{ jenkins_git_host }}:{{ jenkins_git_path }}/{{ repository }}.git') 12 | credentials('{{ jenkins_git_user }}') 13 | } 14 | branch('master') 15 | } 16 | } 17 | scriptPath('Jenkinsfile') 18 | } 19 | } 20 | } 21 | 22 | {% endfor %} 23 | -------------------------------------------------------------------------------- /templates/scriptApproval.xml.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% for signature in jenkins_approved_signatures %} 5 | {{ signature }} 6 | {% endfor %} 7 | 8 | 9 | {% for signature in jenkins_acl_approved_signatures %} 10 | {{ signature }} 11 | {% endfor %} 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /templates/seed-config.xml.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | true 9 | false 10 | false 11 | false 12 | 13 | false 14 | 15 | 16 | {{ lookup('template', jenkins_job_template) }} 17 | 18 | true 19 | false 20 | false 21 | false 22 | false 23 | false 24 | DELETE 25 | DELETE 26 | IGNORE 27 | JENKINS_ROOT 28 | 29 | 30 | 31 | 32 | 33 | --------------------------------------------------------------------------------