├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── defaults └── main.yml ├── filter_plugins ├── .gitignore └── quote_items.py ├── handlers └── main.yml ├── meta └── main.yml ├── tasks ├── main.yml ├── packages_Debian.yml ├── packages_RedHat.yml └── plugins.yml ├── templates ├── configureSshd.groovy.j2 └── etc_default_jenkins_Debian.j2 └── test ├── ansible.cfg ├── docker_platforms ├── centos_7 │ └── Dockerfile ├── ubuntu_14_04 │ └── Dockerfile └── ubuntu_16_04 │ └── Dockerfile ├── install_roles.yml ├── inventory ├── post-test.sh ├── pre-test.sh ├── run-tests.sh ├── templates └── jenkins_hello_world.groovy.j2 ├── test.sh └── test_basic.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore test junk. 2 | test/venv/ 3 | test/roles/ 4 | test/*.retry 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ## 3 | # The Travis CI build for this project. 4 | # 5 | # Yes, it's ironic to build a Jenkins-installation role with its competitor. 6 | # But this is the best option, at least until this project is complete enough 7 | # to be capable of dogfooding itself. 8 | ## 9 | 10 | language: python 11 | python: "2.7.15" 12 | 13 | # The tests will be run in Docker containers, to verify compatibility with 14 | # various OSes. 15 | sudo: required 16 | services: 17 | - docker 18 | 19 | env: 20 | global: 21 | - ROLE=karlmdavis.jenkins2 22 | - CONTAINER_PREFIX=ansible_test_jenkins2 23 | 24 | # Each list item here will be run as a separate test by Travis CI. 25 | matrix: 26 | include: 27 | - env: TEST_PLAY=test_basic.yml PLATFORM=ubuntu_16_04 ANSIBLE_SPEC="ansible" 28 | - env: TEST_PLAY=test_basic.yml PLATFORM=ubuntu_16_04 ANSIBLE_SPEC="ansible==2.4.0.0" 29 | - env: TEST_PLAY=test_basic.yml PLATFORM=ubuntu_14_04 ANSIBLE_SPEC="ansible" 30 | # Testing against CentOS also (mostly) covers RHEL 7. 31 | - env: TEST_PLAY=test_basic.yml PLATFORM=centos_7 ANSIBLE_SPEC="ansible" 32 | 33 | install: 34 | # Generate an SSH key for use when connecting to Docker containers. 35 | - ssh-keygen -t rsa -N '' -f /home/travis/.ssh/id_rsa 36 | 37 | # Prepare to run the tests. 38 | - ./test/pre-test.sh /home/travis/.ssh/id_rsa.pub 39 | 40 | script: 41 | # Run the tests. 42 | - test/test.sh 43 | 44 | after_script: 45 | # Inspect the management host environment a bit post-test. Just to aid in debugging. 46 | - pwd 47 | - ls -la ../ 48 | - ls -la ./ 49 | - ls -la ./roles/ 50 | 51 | # Inspect the Docker container a bit post-test. Just to aid in debugging. 52 | - docker exec ${CONTAINER_PREFIX}.${PLATFORM} ls -la /opt/jenkins 53 | - docker exec ${CONTAINER_PREFIX}.${PLATFORM} ls -la /var/lib/jenkins 54 | - docker exec ${CONTAINER_PREFIX}.${PLATFORM} ls -la /var/lib/jenkins-not-default 55 | - docker exec ${CONTAINER_PREFIX}.${PLATFORM} ls -la /var/lib/jenkins-not-default/init.groovy.d 56 | - docker exec ${CONTAINER_PREFIX}.${PLATFORM} cat /var/log/jenkins/jenkins.log 57 | 58 | # Clean up after the test. 59 | - test/post-test.sh 60 | 61 | notifications: 62 | webhooks: https://galaxy.ansible.com/api/v1/notifications/ 63 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Welcome! 2 | 3 | We're so glad you're thinking about contributing to an HHS IDEA Lab open source project! If you're unsure about anything, just ask -- or submit the issue or pull request anyway. The worst that can happen is you'll be politely asked to change something. We love all friendly contributions. 4 | 5 | We want to ensure a welcoming environment for all of our projects. Our staff follow the [18F Code of Conduct](https://github.com/18F/code-of-conduct/blob/master/code-of-conduct.md) and all contributors should do the same. 6 | 7 | We encourage you to read this project's CONTRIBUTING policy (you are here), its [LICENSE](LICENSE.md), and its [README](README.md). 8 | 9 | If you have any questions or want to read more, check out the [18F Open Source Policy GitHub repository]( https://github.com/18f/open-source-policy), which inspired this policy. 10 | 11 | ## Public domain 12 | 13 | This project is in the public domain within the United States, and 14 | copyright and related rights in the work worldwide are waived through 15 | the [CC0 1.0 Universal public domain dedication](https://creativecommons.org/publicdomain/zero/1.0/). 16 | 17 | All contributions to this project will be released under the CC0 18 | dedication. By submitting a pull request, you are agreeing to comply 19 | with this waiver of copyright interest. 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | As a work of the United States Government, this project is in the 2 | public domain within the United States. 3 | 4 | Additionally, we waive copyright and related rights in the work 5 | worldwide through the CC0 1.0 Universal public domain dedication. 6 | 7 | ## CC0 1.0 Universal Summary 8 | 9 | This is a human-readable summary of the [Legal Code (read the full text)](https://creativecommons.org/publicdomain/zero/1.0/legalcode). 10 | 11 | ### No Copyright 12 | 13 | The person who associated a work with this deed has dedicated the work to 14 | the public domain by waiving all of his or her rights to the work worldwide 15 | under copyright law, including all related and neighboring rights, to the 16 | extent allowed by law. 17 | 18 | You can copy, modify, distribute and perform the work, even for commercial 19 | purposes, all without asking permission. 20 | 21 | ### Other Information 22 | 23 | In no way are the patent or trademark rights of any person affected by CC0, 24 | nor are the rights that other persons may have in the work or in how the 25 | work is used, such as publicity or privacy rights. 26 | 27 | Unless expressly stated otherwise, the person who associated a work with 28 | this deed makes no warranties about the work, and disclaims liability for 29 | all uses of the work, to the fullest extent permitted by applicable law. 30 | When using or citing the work, you should not imply endorsement by the 31 | author or the affirmer. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/karlmdavis/ansible-role-jenkins2.svg?branch=master)](https://travis-ci.org/karlmdavis/ansible-role-jenkins2) 2 | 3 | Ansible Role for Jenkins 2+ 4 | =========================== 5 | 6 | This [Ansible](https://www.ansible.com/) role can be used to install and manage [Jenkins 2](https://jenkins.io/2.0/). 7 | 8 | Requirements 9 | ------------ 10 | 11 | This role requires Ansible 2.4 or later, with either Ansible pipelining available or `setfacl` available on the system being managed (per [Becoming an Unprivileged User](http://docs.ansible.com/ansible/latest/become.html#becoming-an-unprivileged-user)). 12 | 13 | The role currently supports Ubuntu 14.04 (Trusty) and Ubuntu 16.04 (Xenial), though contributtions for additional platform support are welcome! 14 | 15 | Role Variables 16 | -------------- 17 | 18 | This role supports the following variables, listed here with their default values from [defaults/main.yml](defaults/main.yml): 19 | 20 | * `jenkins_release_line`: `'weekly'` 21 | * When set to `long_term_support`, the role will install the LTS releases of Jenkins. 22 | * When set to `weekly`, the role will install the weekly releases of Jenkins. 23 | * `jenkins_release_update`: `true` 24 | * If `true`, the Jenkins package (YUM, APT, etc.) will be upgraded to the latest version when this role is run. 25 | * `jenkins_home`: `/var/lib/jenkins` 26 | * The directory that (most of) Jenkins data will be stored. 27 | * Due to limitations of the Jenkins installer, the `jenkins` service account will still use the default as its home directory. This should really only come into play for SSH keys. 28 | * `jenkins_port`: `8080` 29 | * The port that Jenkins will run on, for HTTP requests. 30 | * On most systems, this value will need to be over 1024, as Jenkins is not run as `root`. 31 | * `jenkins_context_path`: `''` 32 | * The context path that Jenkins will be hosted at, e.g. `/foo` in `http://localhost:8080/foo`. Leave as `''` to host at root path. 33 | * `jenkins_url_external`: `''` 34 | * The external URL that users will use to access Jenkins. Gets set in the Jenkins config and used in emails, webhooks, etc. 35 | * If this is left empty/None, the configuration will not be set and Jenkins will try to auto-discover this (which won't work correctly if it's proxied). 36 | * `jenkins_admin_username`: (undefined) 37 | * If one of `jenkins_admin_username` and `jenkins_admin_password` are defined, both must be. 38 | * Override this variable to specify the Jenkins administrator credentials that should be used for each possible security realm. 39 | * If left undefined, the role will attempt to use anonymous authentication. 40 | * Note that the role will automatically detect if Jenkins is set to allow anonymous authentication (as is the case right after install) and handle it properly. 41 | * `jenkins_admin_password`: (undefined) 42 | * If one of `jenkins_admin_username` and `jenkins_admin_password` are defined, both must be. 43 | * Override this variable to specify the Jenkins administrator credentials that should be used for each possible security realm. 44 | * `jenkins_session_timeout`: `30` 45 | * The number of minutes before Jenkins sessions timeout, i.e. how long logins are valid for. 46 | * Defaults to 30 minutes. 47 | * Can be set to `0` to never timeout. 48 | * `jenkins_plugins_extra`: `[]` 49 | * Override this variable to install additional Jenkins plugins. 50 | * These would be in addition to the plugins recommended by Jenkins 2's new setup wizard, which are installed automatically by this role (see `jenkins_plugins_recommended` in [defaults/main.yml](defaults/main.yml)). 51 | * `jenkins_plugins_timeout`: `60` 52 | * The amount of time (in seconds) before a plugin install/update will fail. This value is passed to the timeout parameter in `jenkins_plugin` module. (See here for details: .) 53 | * `jenkins_plugins_update`: `true` 54 | * If `true`, the Jenkins plugins will be updated when this role is run. (Note that missing plugins will always be installed.) 55 | * `jenkins_java_args_extra`: `''` 56 | * Additional options that will be added to `JAVA_ARGS` for the Jenkins process, such as the JVM memory settings, e.g. `-Xmx4g`. 57 | * `jenkins_http_proxy_server`, `jenkins_http_proxy_port`, `jenkins_http_proxy_no_proxy_hosts`: (all undefined) 58 | * These server the same function as the JVM's `http.proxyHost`, `http.proxyPort`, and `http.nonProxyHosts` system properties, except that the settings will be used for both HTTP and HTTPS requests. 59 | * Specifically, these settings will be used to configure: 60 | * The Jenkins JVM's `http.proxyHost`, `http.proxyPort`, `https.proxyHost`, `https.proxyPort`, and `http.nonProxyHosts` system properties, as documented on [Java Networking and Proxies](https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html). 61 | * The Jenkins-specific proxy settings (which some plugins, such as the [GitHub plugin](https://wiki.jenkins.io/display/JENKINS/Github+Plugin), require), as documented on [JenkinsBehindProxy](https://wiki.jenkins.io/display/JENKINS/JenkinsBehindProxy). 62 | * The value of `jenkins_http_proxy_no_proxy_hosts` should be a list, e.g. `['localhost', 'example.com']`. 63 | 64 | Dependencies 65 | ------------ 66 | 67 | This role does not have direct dependencies on other Ansible roles. However, it does require that a Java JRE be available on the system path. 68 | 69 | Example Playbook 70 | ---------------- 71 | 72 | This role can be installed, as follows: 73 | 74 | $ ansible-galaxy install karlmdavis.jenkins2 75 | 76 | This role can be applied, as follows: 77 | 78 | ```yaml 79 | - hosts: some_box 80 | tasks: 81 | - import_role: 82 | name: karlmdavis.ansible-jenkins2 83 | vars: 84 | jenkins_plugins_extra: 85 | - github-oauth 86 | ``` 87 | 88 | ## Running Groovy Scripts to Configure Jenkins 89 | 90 | After installing Jenkins, Groovy scripts can be run via Ansible to further customize Jenkins. 91 | 92 | For example, here's how to install Jenkins and then configure Jenkins to use its `HudsonPrivateSecurityRealm`, for local Jenkins accounts: 93 | 94 | ```yaml 95 | - hosts: some_box 96 | tasks: 97 | 98 | - import_role: 99 | name: karlmdavis.ansible-jenkins2 100 | vars: 101 | # Won't be required on first run, but will be on prior runs (after 102 | # security has been enabled, per below). 103 | jenkins_admin_username: test 104 | jenkins_admin_password: supersecret 105 | 106 | # Ensure that Jenkins has restarted, if it needs to. 107 | - meta: flush_handlers 108 | 109 | # Configure security to use Jenkins-local accounts. 110 | - name: Configure Security 111 | jenkins_script: 112 | url: "{{ jenkins_url_local }}" 113 | user: "{{ jenkins_dynamic_admin_username | default(omit) }}" 114 | password: "{{ jenkins_dynamic_admin_password | default(omit) }}" 115 | script: | 116 | // These are the basic imports that Jenkin's interactive script console 117 | // automatically includes. 118 | import jenkins.*; 119 | import jenkins.model.*; 120 | import hudson.*; 121 | import hudson.model.*; 122 | 123 | // Configure the security realm, which handles authentication. 124 | def securityRealm = new hudson.security.HudsonPrivateSecurityRealm(false) 125 | if(!securityRealm.equals(Jenkins.instance.getSecurityRealm())) { 126 | Jenkins.instance.setSecurityRealm(securityRealm) 127 | 128 | // Create a user to login with. Ensure that user is bound to the 129 | // system-local `jenkins` user's SSH key, to ensure that this 130 | // account can be used with Jenkins' CLI. 131 | def testUser = securityRealm.createAccount("test", "supersecret") 132 | testUser.addProperty(new hudson.tasks.Mailer.UserProperty("foo@example.com")); 133 | testUser.save() 134 | 135 | Jenkins.instance.save() 136 | println "Changed authentication." 137 | } 138 | 139 | // Configure the authorization strategy, which specifies who can do 140 | // what. 141 | def authorizationStrategy = new hudson.security.FullControlOnceLoggedInAuthorizationStrategy() 142 | if(!authorizationStrategy.equals(Jenkins.instance.getAuthorizationStrategy())) { 143 | authorizationStrategy.setAllowAnonymousRead(false) 144 | Jenkins.instance.setAuthorizationStrategy(authorizationStrategy) 145 | Jenkins.instance.save() 146 | println "Changed authorization." 147 | } 148 | register: shell_jenkins_security 149 | changed_when: "(shell_jenkins_security | success) and 'Changed' not in shell_jenkins_security.stdout" 150 | ``` 151 | 152 | Alternatively, the Groovy scripts can be stored as separate files and pulled in using a `lookup(...)`, as below: 153 | 154 | ```yaml 155 | - hosts: some_box 156 | tasks: 157 | 158 | - import_role: 159 | name: karlmdavis.ansible-jenkins2 160 | vars: 161 | # Won't be required on first run, but will be on prior runs (after 162 | # security has been enabled, per below). 163 | jenkins_admin_username: test 164 | jenkins_admin_password: supersecret 165 | 166 | # Ensure that Jenkins has restarted, if it needs to. 167 | - meta: flush_handlers 168 | 169 | # Configure security to use Jenkins-local accounts. 170 | - name: Configure Security 171 | jenkins_script: 172 | url: "{{ jenkins_url_local }}" 173 | user: "{{ jenkins_dynamic_admin_username | default(omit) }}" 174 | password: "{{ jenkins_dynamic_admin_password | default(omit) }}" 175 | script: "{{ lookup('template', 'templates/jenkins_security.groovy.j2') }}" 176 | ``` 177 | 178 | License 179 | ------- 180 | 181 | This project is in the worldwide [public domain](LICENSE.md). As stated in [CONTRIBUTING](CONTRIBUTING.md): 182 | 183 | > This project is in the public domain within the United States, and copyright and related rights in the work worldwide are waived through the [CC0 1.0 Universal public domain dedication](https://creativecommons.org/publicdomain/zero/1.0/). 184 | > 185 | > All contributions to this project will be released under the CC0 dedication. By submitting a pull request, you are agreeing to comply with this waiver of copyright interest. 186 | 187 | Author Information 188 | ------------------ 189 | 190 | This plugin was authored by Karl M. Davis (https://justdavis.com/karl/). 191 | 192 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Variables in this file are automatically included into the role's context, 3 | # and can be overridden by the active play. 4 | # (Other files in this directory must be included via an `include_vars` task.) 5 | 6 | # Which Jenkins release line to use: must be either 'long_term_support' or 7 | # 'weekly'. 8 | # FIXME: Default should probably be Long Term Support, but that would not be 9 | # backwards compatible with earlier versions of this role. 10 | jenkins_release_line: 'weekly' 11 | 12 | # If `true`, the Jenkins package (YUM, APT, etc.) will be upgraded to the latest 13 | # version when this role is run. 14 | jenkins_release_update: true 15 | 16 | # The directory that will be used as the home directory for the Jenkins service 17 | # account. Cannot be modified, due to limitations in the Jenkins installers. 18 | jenkins_home_default: '/var/lib/jenkins' 19 | 20 | # The location that will be used to store the Jenkins configuration and data. 21 | jenkins_home: "{{ jenkins_home_default }}" 22 | 23 | # Jenkins doesn't (and shouldn't) run as root, so this must be over 1024. 24 | jenkins_port: 8080 25 | 26 | # The context path that Jenkins will be hosted at, e.g. '/foo' in 27 | # 'http://localhost:8080/foo'. Leave as '' to host at root path. 28 | jenkins_context_path: '' 29 | 30 | # The external URL that users will use to access Jenkins. Gets set in the 31 | # Jenkins config and used in emails, webhooks, etc. 32 | # If this is left empty/None, the configuration will not be set and Jenkins 33 | # will try to auto-discover this (which won't work correctly if it's proxied). 34 | jenkins_url_external: '' 35 | 36 | # The number of minutes before Jenkins sessions timeout, i.e. how long logins 37 | # are valid for. Defaults to 30 minutes. Can be set to `0` to never timeout. 38 | # Reference: 39 | jenkins_session_timeout: '30' 40 | 41 | # The plugins that the Jenkins 2.0 setup wizard installs by default. Currently, 42 | # the best place to find this list seems to be in the source, here: 43 | # https://github.com/jenkinsci/jenkins/blob/master/core/src/main/resources/jenkins/install/platform-plugins.json 44 | jenkins_plugins_recommended: 45 | - cloudbees-folder 46 | - antisamy-markup-formatter 47 | - build-timeout 48 | - credentials-binding 49 | - timestamper 50 | - ws-cleanup 51 | - ant 52 | - gradle 53 | - workflow-aggregator 54 | - github-branch-source 55 | - pipeline-github-lib 56 | - pipeline-stage-view 57 | - git 58 | - subversion 59 | - ssh-slaves 60 | - matrix-auth 61 | - pam-auth 62 | - ldap 63 | - email-ext 64 | - mailer 65 | 66 | # The additional plugins that users of this role would like to be installed 67 | # (must be overridden). 68 | jenkins_plugins_extra: [] 69 | 70 | # Timeout when installing/updating plugins (in seconds). 71 | jenkins_plugins_timeout: 60 72 | 73 | # How many times to retry failed plugin installs/upgrades. 74 | jenkins_plugins_retries: 3 75 | 76 | # If `true`, the Jenkins plugins will be updated when this role is run. 77 | # (Note that missing plugins will always be installed.) 78 | jenkins_plugins_update: true 79 | 80 | # Additional options that will be added to JAVA_ARGS 81 | jenkins_java_args_extra: '' 82 | 83 | ## 84 | # These should only be modified if custom/local mirrors of the YUM, APT, and/or 85 | # plugins repositories are being used. 86 | ## 87 | 88 | # The base URL of the custom YUM/APT repo to be used (the portion that's shared 89 | # by the signing key and packages). 90 | jenkins_packages_url_base: 'https://pkg.jenkins.io' 91 | 92 | # Set to `true` to add a `proxy: _none_` setting for the YUM repo. 93 | jenkins_packages_repo_yum_disable_proxy: false 94 | -------------------------------------------------------------------------------- /filter_plugins/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore PyC temp files. 2 | *.pyc 3 | -------------------------------------------------------------------------------- /filter_plugins/quote_items.py: -------------------------------------------------------------------------------- 1 | from ansible import errors, vars 2 | import json 3 | 4 | # Inspired by this StackOverflow answer: 5 | 6 | def quote_items(list_to_quote): 7 | return ["\"%s\"" % list_item for list_item in list_to_quote] 8 | 9 | class FilterModule (object): 10 | def filters(self): 11 | return {"quote_items": quote_items} 12 | 13 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Restart Service 'jenkins' 4 | service: 5 | name: jenkins 6 | state: restarted 7 | # This is a little hack to run more than one task in a handler. 8 | # Reference: http://stackoverflow.com/a/31618968/1851299 9 | notify: Wait for Jenkins HTTP Port 10 | become: true 11 | 12 | - name: Wait for Jenkins HTTP Port 13 | wait_for: 14 | port: "{{ jenkins_port }}" 15 | notify: Wait for Jenkins HTTP OK 16 | # Enforce propagation of the handlers chain: 17 | changed_when: true 18 | 19 | - name: Wait for Jenkins HTTP OK 20 | uri: 21 | url: "http://localhost:{{ jenkins_port }}{{ jenkins_context_path | default('') }}/login" 22 | status_code: 200 23 | environment: 24 | no_proxy: localhost 25 | register: jenkins_login_page 26 | until: jenkins_login_page.status == 200 27 | retries: 60 28 | delay: 1 29 | 30 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Karl M. Davis 3 | description: This Ansible role can be used to install and manage Jenkins 2. 4 | company: HHS IDEA Lab 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # See LICENSE.md and CONTRIBUTING.md 11 | license: CC0 12 | 13 | min_ansible_version: 2.4 14 | 15 | # Optionally specify the branch Galaxy will use when accessing the GitHub 16 | # repo for this role. During role install, if no tags are available, 17 | # Galaxy will use this branch. During import Galaxy will access files on 18 | # this branch. If travis integration is configured, only notification for this 19 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 20 | # (usually master) will be used. 21 | #github_branch: 22 | 23 | # 24 | # Below are all platforms currently available. Just uncomment 25 | # the ones that apply to your role. If you don't see your 26 | # platform on this list, let us know and we'll get it added! 27 | # 28 | platforms: 29 | - name: EL 30 | versions: 31 | # - all 32 | # - 5 33 | # - 6 34 | - 7 35 | #- name: GenericUNIX 36 | # versions: 37 | # - all 38 | # - any 39 | #- name: Solaris 40 | # versions: 41 | # - all 42 | # - 10 43 | # - 11.0 44 | # - 11.1 45 | # - 11.2 46 | # - 11.3 47 | #- name: Fedora 48 | # versions: 49 | # - all 50 | # - 16 51 | # - 17 52 | # - 18 53 | # - 19 54 | # - 20 55 | # - 21 56 | # - 22 57 | # - 23 58 | #- name: opensuse 59 | # versions: 60 | # - all 61 | # - 12.1 62 | # - 12.2 63 | # - 12.3 64 | # - 13.1 65 | # - 13.2 66 | #- name: IOS 67 | # versions: 68 | # - all 69 | # - any 70 | #- name: SmartOS 71 | # versions: 72 | # - all 73 | # - any 74 | #- name: eos 75 | # versions: 76 | # - all 77 | # - Any 78 | #- name: Windows 79 | # versions: 80 | # - all 81 | # - 2012R2 82 | #- name: Amazon 83 | # versions: 84 | # - all 85 | # - 2013.03 86 | # - 2013.09 87 | #- name: GenericBSD 88 | # versions: 89 | # - all 90 | # - any 91 | #- name: Junos 92 | # versions: 93 | # - all 94 | # - any 95 | #- name: FreeBSD 96 | # versions: 97 | # - all 98 | # - 10.0 99 | # - 10.1 100 | # - 10.2 101 | # - 8.0 102 | # - 8.1 103 | # - 8.2 104 | # - 8.3 105 | # - 8.4 106 | # - 9.0 107 | # - 9.1 108 | # - 9.1 109 | # - 9.2 110 | # - 9.3 111 | - name: Ubuntu 112 | versions: 113 | # - all 114 | # - lucid 115 | # - maverick 116 | # - natty 117 | # - oneiric 118 | # - precise 119 | # - quantal 120 | # - raring 121 | # - saucy 122 | - trusty 123 | # - utopic 124 | # - vivid 125 | # - wily 126 | - xenial 127 | #- name: SLES 128 | # versions: 129 | # - all 130 | # - 10SP3 131 | # - 10SP4 132 | # - 11 133 | # - 11SP1 134 | # - 11SP2 135 | # - 11SP3 136 | #- name: GenericLinux 137 | # versions: 138 | # - all 139 | # - any 140 | #- name: NXOS 141 | # versions: 142 | # - all 143 | # - any 144 | #- name: Debian 145 | # versions: 146 | # - all 147 | # - etch 148 | # - jessie 149 | # - lenny 150 | # - sid 151 | # - squeeze 152 | # - stretch 153 | # - wheezy 154 | 155 | galaxy_tags: 156 | # List tags for your role here, one per line. A tag is 157 | # a keyword that describes and categorizes the role. 158 | # Users find roles by searching for tags. Be sure to 159 | # remove the '[]' above if you add tags to this list. 160 | # 161 | # NOTE: A tag is limited to a single word comprised of 162 | # alphanumeric characters. Maximum 20 tags per role. 163 | - development 164 | - Jenkins 165 | - CI 166 | - ContinuousIntegration 167 | - CD 168 | - ContinuousDelivery 169 | 170 | dependencies: [] 171 | # List your role dependencies here, one per line. 172 | # Be sure to remove the '[]' above if you add dependencies 173 | # to this list. 174 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Install all OS packages (including Jenkins itself). 4 | - include_tasks: "packages_{{ ansible_os_family }}.yml" 5 | 6 | - name: Create Jenkins Home 7 | file: 8 | path: "{{ jenkins_home }}" 9 | state: directory 10 | owner: jenkins 11 | group: jenkins 12 | mode: u=rwx,g=rx,o=rx 13 | become: true 14 | 15 | # Fire any pending restart handlers now, as we need Jenkins to be running. 16 | # Otherwise, the config.xml file may not have been created yet. 17 | - meta: flush_handlers 18 | 19 | - name: Read Jenkins Config File 20 | slurp: 21 | src: "{{ jenkins_home }}/config.xml" 22 | become: true 23 | changed_when: false 24 | register: slurp_jenkins_config 25 | 26 | - name: Determine Active Security Settings 27 | set_fact: 28 | # Yes, I'm parsing XML with a regex, and yes that's bad. But it's almost 29 | # certainly "good enough" in this instance, so. 30 | jenkins_active_security_realm: "{{ slurp_jenkins_config.content | b64decode | regex_replace('\n', '') | regex_replace('.*([^<].*).*', '\\1')) == 'true' else false }}" 33 | 34 | - name: Calculate API Connection Variables 35 | set_fact: 36 | # All communication with the Jenkins API will be done over localhost, for security. 37 | jenkins_url_local: "http://localhost:{{ jenkins_port }}{{ jenkins_context_path }}" 38 | # The actual username and password used are dynamic to ensure that anon logins are used post-install (before security has been configured). 39 | jenkins_dynamic_admin_username: "{{ (jenkins_admin_username | default(None)) if (jenkins_security_enabled | bool) else None }}" 40 | jenkins_dynamic_admin_password: "{{ (jenkins_admin_password | default(None)) if (jenkins_security_enabled | bool) else None }}" 41 | 42 | - name: Create Jenkins Init Script Directory 43 | # Reference: https://wiki.jenkins-ci.org/display/JENKINS/Post-initialization+script 44 | file: 45 | path: "{{ jenkins_home }}/init.groovy.d" 46 | state: directory 47 | owner: jenkins 48 | group: jenkins 49 | mode: 0755 50 | become: true 51 | 52 | # Install and/or update plugins. 53 | - import_tasks: "plugins.yml" 54 | 55 | - name: Configure Jenkins (Miscellaneous Settings) 56 | jenkins_script: 57 | url: "{{ jenkins_url_local }}" 58 | user: "{{ jenkins_dynamic_admin_username | default(omit) }}" 59 | password: "{{ jenkins_dynamic_admin_password | default(omit) }}" 60 | script: | 61 | // These are the basic imports that the Jenkins interactive script console 62 | // automatically includes. 63 | import jenkins.*; 64 | import jenkins.model.*; 65 | import hudson.*; 66 | import hudson.model.*; 67 | 68 | // Enable the slave agent port, because most everyone will want it. 69 | // This will use a setter, which will set the value (to a random port) 70 | // and also initialize the slave agent. 71 | if (Jenkins.instance.slaveAgentPort != 0) { 72 | Jenkins.instance.slaveAgentPort=0 73 | println "Changed: slave agent configuration." 74 | } 75 | 76 | // Set the Jenkins external URL, if defined. 77 | // (Hat tip: http://stackoverflow.com/questions/30355079/jenkins-setting-root-url-via-groovy-api.) 78 | def externalUrl = "{{ jenkins_url_external | default('') | trim }}" ?: null 79 | def locationConfig = JenkinsLocationConfiguration.get() 80 | println("Configuring Jenkins External URL (current value: '${locationConfig.url}', target value: '${externalUrl}')...") 81 | if (externalUrl != locationConfig.url) { 82 | locationConfig.url = externalUrl 83 | locationConfig.save() 84 | println "Changed: external URL." 85 | } 86 | 87 | // Set the Jenkins proxy config, if defined. 88 | def proxyName = "{{ jenkins_http_proxy_server | default('') | trim }}" 89 | def proxyPortText = "{{ jenkins_http_proxy_port | default('') | trim }}" 90 | def proxyNoProxyHosts = "{{ jenkins_http_proxy_no_proxy_hosts | default([]) | join('\n') }}" 91 | if (proxyName == "") { 92 | if (Jenkins.instance.proxy != null) { 93 | Jenkins.instance.proxy = null 94 | ProxyConfiguration.getXmlFile().delete() 95 | println "Changed: removed proxy configuration" 96 | } 97 | } else if ( 98 | Jenkins.instance.proxy == null 99 | || Jenkins.instance.proxy.name != proxyName 100 | || "" + Jenkins.instance.proxy.port != proxyPortText 101 | || Jenkins.instance.proxy.noProxyHost != proxyNoProxyHosts 102 | ) { 103 | try { proxyPort = Integer.parseInt(proxyPortText) } catch(NumberFormatException e) { throw new IllegalArgumentException("Invalid proxy port: ${proxyPortText}", e) } 104 | proxyDesired = new ProxyConfiguration(proxyName, proxyPort, null, null, proxyNoProxyHosts) 105 | proxyDesired.save() 106 | Jenkins.instance.proxy = ProxyConfiguration.load() 107 | println "Changed: proxy configuration." 108 | } 109 | register: shell_jenkins_config_misc 110 | changed_when: "(shell_jenkins_config_misc | success) and 'Changed' in shell_jenkins_config_misc.output" 111 | 112 | - name: Configure Security Recommendations 113 | jenkins_script: 114 | url: "{{ jenkins_url_local }}" 115 | user: "{{ jenkins_dynamic_admin_username | default(omit) }}" 116 | password: "{{ jenkins_dynamic_admin_password | default(omit) }}" 117 | script: | 118 | // These are the basic imports that Jenkins interactive script console 119 | // automatically includes. 120 | import jenkins.*; 121 | import jenkins.model.*; 122 | import hudson.*; 123 | import hudson.model.*; 124 | 125 | // Disable deprecated agent protocols. 126 | // Reference: 127 | def agentProtocolWasDisabled = false 128 | def agentProtocolNames = new HashSet(Jenkins.instance.getAgentProtocols()) 129 | jenkins.AgentProtocol.all().each { agentProtocol -> 130 | println "Checking: " + agentProtocol.name 131 | if (agentProtocol.isDeprecated() && agentProtocolNames.contains(agentProtocol.name)) { 132 | agentProtocolNames.remove(agentProtocol.name) 133 | agentProtocolWasDisabled = true 134 | println "Changed protocols: removed protocol: " + agentProtocol.name 135 | } 136 | } 137 | if (agentProtocolWasDisabled) { 138 | Jenkins.instance.setAgentProtocols(agentProtocolNames) 139 | Jenkins.instance.save() 140 | } 141 | 142 | // Enable agent-master access control, as its strongly recommended. 143 | // References: 144 | // * 145 | // * 146 | if (Jenkins.instance.injector.getInstance(jenkins.security.s2m.AdminWhitelistRule.class).getMasterKillSwitch()) { 147 | Jenkins.instance.injector.getInstance(jenkins.security.s2m.AdminWhitelistRule.class).setMasterKillSwitch(false); 148 | Jenkins.instance.save() 149 | println "Changed agent-master access control: now disabled." 150 | } 151 | 152 | // Enable CSRF, as its strongly recommended. 153 | // Reference: 154 | if (Jenkins.instance.getCrumbIssuer() == null ) { 155 | Jenkins.instance.setCrumbIssuer(new hudson.security.csrf.DefaultCrumbIssuer(true)) 156 | Jenkins.instance.save() 157 | println "Changed CSRF: enabled." 158 | } 159 | register: shell_jenkins_security_recommendations 160 | changed_when: "(shell_jenkins_security_recommendations | success) and 'Changed' in shell_jenkins_security_recommendations.output" 161 | 162 | # Fire any pending restart handlers now, or else the tests below here may not 163 | # be valid. 164 | - meta: flush_handlers 165 | 166 | - name: Ensure Service 'jenkins' Is Running 167 | service: 168 | name: jenkins 169 | state: started 170 | enabled: yes 171 | become: true 172 | 173 | - name: Grab Jenkins Web UI Content 174 | # By default, Jenkins 2 is set to require auth, so the only page we can grab 175 | # without getting a 403 and redirect is the login page. 176 | uri: 177 | url: "http://localhost:{{ jenkins_port }}{{ jenkins_context_path | default('') }}/login" 178 | return_content: true 179 | environment: 180 | no_proxy: localhost 181 | register: jenkins_ui 182 | 183 | - name: Verify Jenkins Web UI Content 184 | action: fail 185 | when: "'Jenkins' not in jenkins_ui.content" 186 | -------------------------------------------------------------------------------- /tasks/packages_Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update APT Cache 3 | apt: 4 | cache_valid_time: "{{ '86400' if jenkins_release_update else '' }}" 5 | become: true 6 | 7 | - name: Install OS Dependencies 8 | apt: name={{ item }} state=installed 9 | become: true 10 | with_items: 11 | # Allows use of HTTPS APT repos. 12 | - apt-transport-https 13 | 14 | - name: Determine APT Key and Repo to Use (Step 1) 15 | set_fact: 16 | jenkins_apt_repo_flavor: "{{ 'debian' if jenkins_release_line == 'weekly' else 'debian-stable' }}" 17 | 18 | - name: Determine APT Key and Repo to Use (Step 2) 19 | set_fact: 20 | jenkins_apt_key_url: "{{ '%s/%s/jenkins.io.key' % (jenkins_packages_url_base, jenkins_apt_repo_flavor) }}" 21 | jenkins_apt_repo: "{{ 'deb %s/%s binary/' % (jenkins_packages_url_base, jenkins_apt_repo_flavor) }}" 22 | 23 | - name: Add Jenkins APT Key 24 | apt_key: 25 | url: "{{ jenkins_apt_key_url }}" 26 | state: present 27 | become: true 28 | 29 | - name: Remove Unused Jenkins APT Repositories 30 | apt_repository: 31 | repo: "{{ item }}" 32 | state: absent 33 | update_cache: true 34 | become: true 35 | with_items: 36 | - 'deb http://pkg.jenkins-ci.org/debian binary/' 37 | - 'deb https://pkg.jenkins.io/debian binary/' 38 | - 'deb https://pkg.jenkins.io/debian-stable binary/' 39 | when: "item != jenkins_apt_repo" 40 | 41 | - name: Add Jenkins APT Repository 42 | apt_repository: 43 | repo: "{{ jenkins_apt_repo }}" 44 | state: present 45 | update_cache: yes 46 | become: true 47 | 48 | # The setup wizard has to be disabled before Jenkins is installed, because the 49 | # APT packages launch it immediately after install. 50 | - name: Configure Jenkins Launch Settings 51 | template: 52 | src: etc_default_jenkins_Debian.j2 53 | dest: /etc/default/jenkins 54 | owner: root 55 | group: root 56 | mode: u=rw,g=,o=r 57 | become: true 58 | notify: 59 | - "Restart Service 'jenkins'" 60 | 61 | - name: Jenkins Install 62 | apt: 63 | name: jenkins 64 | # Setting this to 'latest' is perhaps debatable, but it seems silly not to 65 | # since this plugin also updates all of the Jenkins plugins automatically. 66 | state: "{{ 'latest' if jenkins_release_update else 'present' }}" 67 | become: true 68 | -------------------------------------------------------------------------------- /tasks/packages_RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Determine YUM Repo to Use (Step 1) 3 | set_fact: 4 | jenkins_yum_repo_flavor: "{{ 'redhat' if jenkins_release_line == 'weekly' else 'redhat-stable' }}" 5 | 6 | - name: Determine YUM Repo to Use (Step 2) 7 | set_fact: 8 | jenkins_yum_gpgkey_url: "{{ '%s/%s/jenkins.io.key' % (jenkins_packages_url_base, jenkins_yum_repo_flavor) }}" 9 | jenkins_yum_repo_baseurl: "{{ '%s/%s' % (jenkins_packages_url_base, jenkins_yum_repo_flavor) }}" 10 | jenkins_yum_repo_name: "{{ 'Jenkins' if jenkins_release_line == 'weekly' else 'Jenkins-stable' }}" 11 | 12 | - name: Remove Unused Jenkins YUM Repositories 13 | yum_repository: 14 | name: "{{ item }}" 15 | state: absent 16 | become: true 17 | with_items: 18 | - 'Jenkins' 19 | - 'Jenkins-stable' 20 | when: "item != jenkins_yum_repo_name" 21 | 22 | - name: Add Jenkins YUM Repository 23 | yum_repository: 24 | name: "{{ jenkins_yum_repo_name }}" 25 | description: 'Jenkins release repo from https://jenkins.io/download/.' 26 | baseurl: "{{ jenkins_yum_repo_baseurl }}" 27 | gpgcheck: true 28 | gpgkey: "{{ jenkins_yum_gpgkey_url }}" 29 | proxy: "{{ '_none_' if jenkins_packages_repo_yum_disable_proxy == true else omit }}" 30 | state: present 31 | become: true 32 | 33 | - name: Jenkins Install 34 | yum: 35 | name: jenkins 36 | # Setting this to 'latest' is perhaps debatable, but it seems silly not to 37 | # since this plugin also updates all of the Jenkins plugins automatically. 38 | state: "{{ 'latest' if jenkins_release_update else 'present' }}" 39 | update_cache: "{{ true if jenkins_release_update else false }}" 40 | become: true 41 | 42 | - name: Configure Jenkins Launch Settings 43 | lineinfile: 44 | path: /etc/sysconfig/jenkins 45 | regexp: "{{ item.regexp }}" 46 | line: "{{ item.line }}" 47 | mode: u=rw,g=,o=r 48 | become: true 49 | with_items: 50 | - regexp: '^JENKINS_JAVA_OPTIONS=' 51 | # Note: The setup wizard has to be disabled before Jenkins runs the first 52 | # time. Fortunately, the RHEL packages don't automatically start the 53 | # Jenkins service after install, so we have time to address this. 54 | line: "JENKINS_JAVA_OPTIONS=\"-Djava.awt.headless=true -Djenkins.install.runSetupWizard=false {{ '' if (jenkins_http_proxy_server | default('') | trim) == '' else '-Dhttp.proxyHost={0} -Dhttps.proxyHost={0}'.format(jenkins_http_proxy_server) }} {{ '' if (jenkins_http_proxy_port | default('') | trim) == '' else '-Dhttp.proxyPort={0} -Dhttps.proxyPort={0}'.format(jenkins_http_proxy_port) }} {{ '' if (jenkins_http_proxy_no_proxy_hosts | default('') | trim) == '' else '-Dhttp.nonProxyHosts={}'.format(jenkins_http_proxy_no_proxy_hosts | join('|')) }} -DsessionTimeout={{ jenkins_session_timeout }} {{ jenkins_java_args_extra }}\"" 55 | - regexp: '^JENKINS_PORT=' 56 | line: "JENKINS_PORT={{ jenkins_port }}" 57 | - regexp: '^JENKINS_HOME=' 58 | line: "JENKINS_HOME=\"{{ jenkins_home }}\"" 59 | - regexp: '^JENKINS_ARGS=' 60 | line: "JENKINS_ARGS=\"--prefix={{ jenkins_context_path }}\"" 61 | notify: 62 | - "Restart Service 'jenkins'" 63 | -------------------------------------------------------------------------------- /tasks/plugins.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Installs the "standard" Jenkins 2.0 plugins, as well as any additional that 3 | # are specified in the 'plugins_extra' variable. 4 | 5 | - name: Install Plugins 6 | jenkins_plugin: 7 | name: "{{ item }}" 8 | state: present 9 | jenkins_home: "{{ jenkins_home }}" 10 | url: "{{ jenkins_url_local }}" 11 | url_username: "{{ jenkins_dynamic_admin_username | default(omit) }}" 12 | url_password: "{{ jenkins_dynamic_admin_password | default(omit) }}" 13 | validate_certs: "{{ false if ansible_distribution_release == 'trusty' else true }}" 14 | timeout: "{{ jenkins_plugins_timeout }}" 15 | with_items: 16 | - "{{ jenkins_plugins_recommended }}" 17 | - "{{ jenkins_plugins_extra }}" 18 | become: true 19 | retries: "{{ jenkins_plugins_retries }}" 20 | notify: 21 | - "Restart Service 'jenkins'" 22 | 23 | - name: Update Plugins 24 | jenkins_plugin: 25 | name: "{{ item }}" 26 | state: latest 27 | jenkins_home: "{{ jenkins_home }}" 28 | url: "{{ jenkins_url_local }}" 29 | url_username: "{{ jenkins_dynamic_admin_username | default(omit) }}" 30 | url_password: "{{ jenkins_dynamic_admin_password | default(omit) }}" 31 | validate_certs: "{{ false if ansible_distribution_release == 'trusty' else true }}" 32 | timeout: "{{ jenkins_plugins_timeout }}" 33 | with_items: 34 | - "{{ jenkins_plugins_recommended }}" 35 | - "{{ jenkins_plugins_extra }}" 36 | become: true 37 | when: jenkins_plugins_update 38 | retries: "{{ jenkins_plugins_retries }}" 39 | notify: 40 | - "Restart Service 'jenkins'" 41 | -------------------------------------------------------------------------------- /templates/configureSshd.groovy.j2: -------------------------------------------------------------------------------- 1 | // 2 | // This script must be included in `$JENKINS_HOME/init.d.groovy`, as it's a 3 | // pre-req for the Jenkins CLI. 4 | // 5 | 6 | // These are the basic imports that Jenkin's interactive script console 7 | // automatically includes. 8 | import jenkins.*; 9 | import jenkins.model.*; 10 | import hudson.*; 11 | import hudson.model.*; 12 | 13 | // Ensure that SSH access is enabled (on a randomized port). 14 | def sshModuleDescriptor = Jenkins.instance.getDescriptor("org.jenkinsci.main.modules.sshd.SSHD") 15 | sshModuleDescriptor.setPort(0) 16 | sshModuleDescriptor.getActualPort() // Not sure why, but seems to be needed. 17 | sshModuleDescriptor.save() 18 | -------------------------------------------------------------------------------- /templates/etc_default_jenkins_Debian.j2: -------------------------------------------------------------------------------- 1 | # defaults for Jenkins automation server 2 | 3 | # pulled in from the init script; makes things easier. 4 | NAME=jenkins 5 | 6 | # location of java 7 | JAVA=/usr/bin/java 8 | 9 | # arguments to pass to java 10 | 11 | # Allow graphs etc. to work even when an X server is present 12 | JAVA_ARGS="-Djava.awt.headless=true" 13 | 14 | # Ensure that the setup wizard is disabled; that configuration is handled by 15 | # Ansible. 16 | JAVA_ARGS="${JAVA_ARGS} -Djenkins.install.runSetupWizard=false" 17 | 18 | # Specify addition JVM HTTP/HTTPS proxy system properties, if configured by the Ansible role. 19 | JAVA_ARGS="${JAVA_ARGS}{{ '' if (jenkins_http_proxy_server | default('') | trim) == '' else ' -Dhttp.proxyHost={0} -Dhttps.proxyHost={0}'.format(jenkins_http_proxy_server) }}" 20 | JAVA_ARGS="${JAVA_ARGS}{{ '' if (jenkins_http_proxy_port | default('') | trim) == '' else ' -Dhttp.proxyPort={0} -Dhttps.proxyPort={0}'.format(jenkins_http_proxy_port) }}" 21 | JAVA_ARGS="${JAVA_ARGS}{{ '' if (jenkins_http_proxy_no_proxy_hosts | default('') | trim) == '' else ' -Dhttp.nonProxyHosts={}'.format(jenkins_http_proxy_no_proxy_hosts) | join('|') }}" 22 | 23 | # Set the session/login timeout. 24 | JAVA_ARGS="${JAVA_ARGS} -DsessionTimeout={{ jenkins_session_timeout }}" 25 | 26 | # Additional args specified by user of Ansible role. 27 | JAVA_ARGS="${JAVA_ARGS} {{ jenkins_java_args_extra }}" 28 | 29 | #JAVA_ARGS="-Xmx256m" 30 | 31 | # make jenkins listen on IPv4 address 32 | #JAVA_ARGS="-Djava.net.preferIPv4Stack=true" 33 | 34 | PIDFILE=/var/run/$NAME/$NAME.pid 35 | 36 | # user and group to be invoked as (default to jenkins) 37 | JENKINS_USER=$NAME 38 | JENKINS_GROUP=$NAME 39 | 40 | # location of the jenkins war file 41 | JENKINS_WAR=/usr/share/$NAME/$NAME.war 42 | 43 | # jenkins home location 44 | JENKINS_HOME={{ jenkins_home }} 45 | 46 | # set this to false if you don't want Jenkins to run by itself 47 | # in this set up, you are expected to provide a servlet container 48 | # to host jenkins. 49 | RUN_STANDALONE=true 50 | 51 | # log location. this may be a syslog facility.priority 52 | JENKINS_LOG=/var/log/$NAME/$NAME.log 53 | #JENKINS_LOG=daemon.info 54 | 55 | # OS LIMITS SETUP 56 | # comment this out to observe /etc/security/limits.conf 57 | # this is on by default because http://github.com/jenkinsci/jenkins/commit/2fb288474e980d0e7ff9c4a3b768874835a3e92e 58 | # reported that Ubuntu's PAM configuration doesn't include pam_limits.so, and as a result the # of file 59 | # descriptors are forced to 1024 regardless of /etc/security/limits.conf 60 | MAXOPENFILES=8192 61 | 62 | # set the umask to control permission bits of files that Jenkins creates. 63 | # 027 makes files read-only for group and inaccessible for others, which some security sensitive users 64 | # might consider benefitial, especially if Jenkins runs in a box that's used for multiple purposes. 65 | # Beware that 027 permission would interfere with sudo scripts that run on the master (JENKINS-25065.) 66 | # 67 | # Note also that the particularly sensitive part of $JENKINS_HOME (such as credentials) are always 68 | # written without 'others' access. So the umask values only affect job configuration, build records, 69 | # that sort of things. 70 | # 71 | # If commented out, the value from the OS is inherited, which is normally 022 (as of Ubuntu 12.04, 72 | # by default umask comes from pam_umask(8) and /etc/login.defs 73 | 74 | # UMASK=027 75 | 76 | # port for HTTP connector (default 8080; disable with -1) 77 | HTTP_PORT={{ jenkins_port }} 78 | 79 | 80 | # servlet context, important if you want to use apache proxying 81 | PREFIX={{ jenkins_context_path }} 82 | 83 | # arguments to pass to jenkins. 84 | # --javahome=$JAVA_HOME 85 | # --httpListenAddress=$HTTP_HOST (default 0.0.0.0) 86 | # --httpPort=$HTTP_PORT (default 8080; disable with -1) 87 | # --httpsPort=$HTTP_PORT 88 | # --argumentsRealm.passwd.$ADMIN_USER=[password] 89 | # --argumentsRealm.roles.$ADMIN_USER=admin 90 | # --webroot=~/.jenkins/war 91 | # --prefix=$PREFIX 92 | 93 | JENKINS_ARGS="--webroot=/var/cache/$NAME/war --httpPort=$HTTP_PORT --prefix=$PREFIX" 94 | -------------------------------------------------------------------------------- /test/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | 3 | # Ensure that we can connect without being prompted to verify host keys. 4 | # Reference: http://docs.ansible.com/ansible/latest/intro_getting_started.html#host-key-checking 5 | host_key_checking = False 6 | 7 | # Docker's AUFS and OverlayFS storage drivers don't support setfacl. This 8 | # should only be used in tests; **DO NOT** include this seeting in production 9 | # environments. 10 | allow_world_readable_tmpfiles = True 11 | 12 | # Log how long each Ansible task takes to run. 13 | # Reference: http://stackoverflow.com/a/29132716/1851299 14 | callback_whitelist = profile_tasks 15 | 16 | # Retry files haven't proven useful for me yet, so just disable them. 17 | retry_files_enabled = False 18 | 19 | [ssh_connection] 20 | pipelining = True 21 | -------------------------------------------------------------------------------- /test/docker_platforms/centos_7/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | ENV container docker 3 | 4 | # Getting systemd to run correctly inside Docker is very tricky. Need to 5 | # ensure that it doesn't start things it shouldn't, without stripping out so 6 | # much as to make it useless. 7 | # 8 | # References: 9 | # 10 | # * : Good start, but badly broken. 11 | # * : For Ubuntu, but works! 12 | # * : Also some useful info. 13 | RUN find /etc/systemd/system \ 14 | /lib/systemd/system \ 15 | -path '*.wants/*' \ 16 | -not -name '*journald*' \ 17 | -not -name '*systemd-tmpfiles*' \ 18 | -not -name '*systemd-user-sessions*' \ 19 | -exec rm \{} \; 20 | RUN systemctl set-default multi-user.target 21 | STOPSIGNAL SIGRTMIN+3 22 | 23 | # Install and start SSH. 24 | # 25 | # References: 26 | # 27 | # * 28 | RUN yum update -y 29 | RUN yum install -y openssh-server sudo initscripts 30 | RUN /usr/bin/systemctl enable sshd 31 | RUN /usr/bin/systemctl enable systemd-user-sessions.service 32 | 33 | # Create the SSH user. 34 | RUN useradd ansible_test 35 | RUN echo 'ansible_test:secret' | chpasswd 36 | RUN echo 'ansible_test ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 37 | 38 | VOLUME [ "/sys/fs/cgroup" ] 39 | EXPOSE 22 40 | CMD ["/usr/sbin/init"] 41 | 42 | -------------------------------------------------------------------------------- /test/docker_platforms/ubuntu_14_04/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu-upstart:14.04 2 | ENV container docker 3 | 4 | # Create the SSH user. 5 | RUN adduser ansible_test && adduser ansible_test sudo 6 | RUN echo 'ansible_test ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/ansible_test 7 | RUN echo 'ansible_test:secret' | chpasswd 8 | 9 | # Ensure that Python 2.7 is installed, for Ansible. 10 | RUN apt-get update && apt-get install -y python2.7 python 11 | 12 | EXPOSE 22 13 | CMD ["/sbin/init"] 14 | -------------------------------------------------------------------------------- /test/docker_platforms/ubuntu_16_04/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | ENV container docker 3 | 4 | # Getting systemd to run correctly inside Docker is very tricky. Need to 5 | # ensure that it doesn't start things it shouldn't, without stripping out so 6 | # much as to make it useless. 7 | # 8 | # References: 9 | # 10 | # * : Good start, but badly broken. 11 | # * : For Ubuntu, but works! 12 | RUN find /etc/systemd/system \ 13 | /lib/systemd/system \ 14 | -path '*.wants/*' \ 15 | -not -name '*journald*' \ 16 | -not -name '*systemd-tmpfiles*' \ 17 | -not -name '*systemd-user-sessions*' \ 18 | -exec rm \{} \; 19 | RUN systemctl set-default multi-user.target 20 | STOPSIGNAL SIGRTMIN+3 21 | 22 | # Install and start SSH. 23 | # 24 | # References: 25 | # 26 | # * 27 | RUN apt-get update && apt-get install -y openssh-server sudo 28 | RUN systemctl enable ssh 29 | 30 | # Create the SSH user. 31 | RUN adduser ansible_test && adduser ansible_test sudo 32 | RUN echo 'ansible_test ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/ansible_test 33 | RUN echo 'ansible_test:secret' | chpasswd 34 | 35 | # Ensure that Python 2.7 is installed, for Ansible. 36 | RUN apt-get update && apt-get install -y python2.7 python 37 | 38 | VOLUME [ "/sys/fs/cgroup" ] 39 | EXPOSE 22 40 | CMD ["/sbin/init"] 41 | 42 | -------------------------------------------------------------------------------- /test/install_roles.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Install Oracle's Java 8 JDK 3 | # https://galaxy.ansible.com/geerlingguy/java/ 4 | - src: geerlingguy.java 5 | -------------------------------------------------------------------------------- /test/inventory: -------------------------------------------------------------------------------- 1 | docker_container ansible_host=localhost ansible_port=13022 ansible_user=ansible_test 2 | -------------------------------------------------------------------------------- /test/post-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Stop immediately if any command returns a non-zero result. 4 | set -e 5 | 6 | # Determine the directory that this script is in. 7 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | 9 | # Run everything from that directory. 10 | cd "${SCRIPT_DIR}" 11 | 12 | # Remove the Docker instance used in the tests. 13 | sudo docker rm --force ${CONTAINER_PREFIX}.${PLATFORM} 14 | -------------------------------------------------------------------------------- /test/pre-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Stop immediately if any command returns a non-zero result. 4 | set -e 5 | 6 | # Determine the directory that this script is in. 7 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | 9 | # Run everything from that directory. 10 | cd "${SCRIPT_DIR}" 11 | 12 | # Get the SSH public key to use from the args passed in. 13 | sshPublicKey="$1" 14 | if [[ ! -f "${sshPublicKey}" ]]; then 15 | echo "SSH key not found: '${sshPublicKey}'." 1>&2 16 | exit 1 17 | fi 18 | 19 | # Create and activate the Python virtualenv needed by Ansible. 20 | if [[ ! -d venv/ ]]; then 21 | virtualenv -p $(which python2) venv 22 | fi 23 | source venv/bin/activate 24 | 25 | # Install Ansible into the venv. 26 | pip install --upgrade setuptools 27 | pip install "${ANSIBLE_SPEC}" 28 | 29 | # Install any requirements needed by the role or its tests. 30 | if [[ -f ../requirements.txt ]]; then pip install --requirement ../requirements.txt; fi 31 | if [[ -f requirements.txt ]]; then pip install --requirement requirements.txt; fi 32 | 33 | # Prep the Ansible roles that the test will use. 34 | if [[ ! -d roles ]]; then mkdir roles; fi 35 | if [[ ! -x "roles/${ROLE}" ]]; then ln -s "$(cd .. && pwd)" "roles/${ROLE}"; fi 36 | if [[ -f ../install_roles.yml ]]; then ansible-galaxy --role-file=../install_roles.yml --roles-path=./roles; fi 37 | if [[ -f install_roles.yml ]]; then ansible-galaxy install --role-file=install_roles.yml --roles-path=./roles; fi 38 | 39 | # Prep the Docker container that will be used (if it's not already running). 40 | if [[ $(sudo docker ps -f "name=${CONTAINER_PREFIX}.${PLATFORM}" --format '{{.Names}}') != "${CONTAINER_PREFIX}.${PLATFORM}" ]]; then 41 | sudo docker build \ 42 | --tag ${CONTAINER_PREFIX}/${PLATFORM} \ 43 | docker_platforms/${PLATFORM} 44 | sudo docker run \ 45 | --cap-add=SYS_ADMIN \ 46 | --detach \ 47 | --publish 127.0.0.1:13022:22 \ 48 | --volume=/sys/fs/cgroup:/sys/fs/cgroup:ro \ 49 | --tmpfs /run \ 50 | --tmpfs /run/lock \ 51 | --name ${CONTAINER_PREFIX}.${PLATFORM} \ 52 | ${CONTAINER_PREFIX}/${PLATFORM} 53 | cat "${sshPublicKey}" | sudo docker exec \ 54 | --interactive ${CONTAINER_PREFIX}.${PLATFORM} \ 55 | /bin/bash -c "mkdir /home/ansible_test/.ssh && cat >> /home/ansible_test/.ssh/authorized_keys" 56 | fi 57 | -------------------------------------------------------------------------------- /test/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## 4 | # This script runs the equivalent of (one of) the Travis CI test cases locally. 5 | ## 6 | 7 | # Stop immediately if any command returns a non-zero result. 8 | set -e 9 | 10 | # These same variables are defined in `.travis.yml`. They can be adjusted to 11 | # change which test is run. 12 | export ROLE=karlmdavis.jenkins2 13 | export CONTAINER_PREFIX=ansible_test_jenkins2 14 | export TEST_PLAY=test_basic.yml 15 | export PLATFORM=ubuntu_14_04 16 | #export PLATFORM=centos_7 17 | export ANSIBLE_SPEC="ansible" 18 | 19 | # Determine the directory that this script is in. 20 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 21 | 22 | # The SSH public key to use when connecting to Docker containers. 23 | sshPublicKey="$(eval echo ~)/.ssh/id_rsa.pub" 24 | if [[ ! -f "${sshPublicKey}" ]]; then 25 | echo "No SSH public key available." 1>&2 26 | exit 1 27 | fi 28 | 29 | # Run the equivalent of Travis' `install` phase (prepares to run the tests). 30 | "${SCRIPT_DIR}/pre-test.sh" "${sshPublicKey}" 31 | 32 | # Run the equivalent of Travis' `script` phase (runs the actualt tests). 33 | "${SCRIPT_DIR}/test.sh" 34 | 35 | # Run the equivalent of Travis' `after_script` phase (cleans up after the 36 | # tests). 37 | "${SCRIPT_DIR}/post-test.sh" 38 | 39 | echo "" 40 | echo "Tests completed successfully." 41 | -------------------------------------------------------------------------------- /test/templates/jenkins_hello_world.groovy.j2: -------------------------------------------------------------------------------- 1 | println "Hello World!" 2 | 3 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Stop immediately if any command returns a non-zero result. 4 | set -e 5 | 6 | # Determine the directory that this script is in. 7 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | 9 | # Run everything from that directory. 10 | cd "${SCRIPT_DIR}" 11 | 12 | # Activate the Python virtual env. 13 | source venv/bin/activate 14 | 15 | # Basic role syntax check 16 | ansible-playbook $TEST_PLAY --inventory=inventory --syntax-check 17 | 18 | # Run the Ansible test case. 19 | ansible-playbook $TEST_PLAY --inventory=inventory 20 | 21 | # Run the role/playbook again, checking to make sure it's idempotent. 22 | ansible-playbook $TEST_PLAY --inventory=inventory \ 23 | | tee /dev/tty \ 24 | | grep -q 'changed=0.*failed=0' \ 25 | && (echo 'Idempotence test: pass' && exit 0) \ 26 | || (echo 'Idempotence test: fail' && exit 1) 27 | -------------------------------------------------------------------------------- /test/test_basic.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ## 3 | # Installs and verifies Jenkins on the container being used for the test case. 4 | ## 5 | 6 | - hosts: docker_container 7 | become: true 8 | tasks: 9 | # Prepare to install Java 8 (depending on OS, may not need to do anything). 10 | - block: 11 | - name: Add the webupd8team/java PPA for Ubuntu 14.04 12 | apt_repository: 13 | repo: 'ppa:webupd8team/java' 14 | - name: Accept Oracle Java License 15 | debconf: 16 | name: oracle-java8-installer 17 | question: "{{ item.question }}" 18 | vtype: "{{ item.vtype }}" 19 | value: "{{ item.value }}" 20 | with_items: 21 | - { question: 'shared/accepted-oracle-license-v1-1', vtype: 'select', value: 'true' } 22 | when: 23 | - ansible_distribution == 'Ubuntu' 24 | - ansible_distribution_version == '14.04' 25 | # Have to use Oracle's JDK on Ubuntu 14.04, as OpenJDK wasn't working 26 | # correctly with ca-certificates-java (the 27 | # `/etc/ca-certificates/update.d/jks-keystore` script was hanging). 28 | - import_role: 29 | name: geerlingguy.java 30 | vars: 31 | java_packages: ['oracle-java8-installer'] 32 | when: 33 | - ansible_distribution == 'Ubuntu' 34 | - ansible_distribution_version == '14.04' 35 | - import_role: 36 | name: geerlingguy.java 37 | vars: 38 | java_packages: ['openjdk-8-jdk'] 39 | when: 40 | - ansible_distribution == 'Ubuntu' 41 | - ansible_distribution_version == '16.04' 42 | - import_role: 43 | name: geerlingguy.java 44 | vars: 45 | java_packages: ['java-1.8.0-openjdk'] 46 | when: 47 | - ansible_os_family == 'RedHat' 48 | 49 | # Install Jenkins. 50 | - hosts: docker_container 51 | tasks: 52 | - import_role: 53 | name: karlmdavis.jenkins2 54 | vars: 55 | # Just setting something here so we can verify it's working. 56 | jenkins_home: /var/lib/jenkins-not-default 57 | 58 | # This test user gets created below. 59 | jenkins_admin_username: test 60 | jenkins_admin_password: supersecret 61 | 62 | # Ensure that Jenkins has restarted, if it needs to. 63 | - meta: flush_handlers 64 | 65 | # Configure security, so that we can verify that things still work with it. 66 | - name: Configure Security 67 | jenkins_script: 68 | url: "{{ jenkins_url_local }}" 69 | user: "{{ jenkins_dynamic_admin_username | default(omit) }}" 70 | password: "{{ jenkins_dynamic_admin_password | default(omit) }}" 71 | script: | 72 | // These are the basic imports that Jenkins interactive script console 73 | // automatically includes. 74 | import jenkins.*; 75 | import jenkins.model.*; 76 | import hudson.*; 77 | import hudson.model.*; 78 | 79 | def securityRealm = new hudson.security.HudsonPrivateSecurityRealm(false) 80 | if(!securityRealm.equals(Jenkins.instance.getSecurityRealm())) { 81 | Jenkins.instance.setSecurityRealm(securityRealm) 82 | 83 | def testUser = securityRealm.createAccount("test", "supersecret") 84 | testUser.addProperty(new hudson.tasks.Mailer.UserProperty("foo@example.com")); 85 | testUser.save() 86 | 87 | Jenkins.instance.save() 88 | println "Changed authentication." 89 | } 90 | 91 | def authorizationStrategy = new hudson.security.FullControlOnceLoggedInAuthorizationStrategy() 92 | if(!authorizationStrategy.equals(Jenkins.instance.getAuthorizationStrategy())) { 93 | authorizationStrategy.setAllowAnonymousRead(false) 94 | Jenkins.instance.setAuthorizationStrategy(authorizationStrategy) 95 | Jenkins.instance.save() 96 | println "Changed authorization." 97 | } 98 | register: shell_jenkins_security 99 | changed_when: "(shell_jenkins_security | success) and 'Changed' not in shell_jenkins_security.output" 100 | 101 | # Verify that things worked as expected. 102 | - hosts: docker_container 103 | tasks: 104 | - name: Ensure Service 'jenkins' Is Running 105 | service: 106 | name: jenkins 107 | state: started 108 | enabled: yes 109 | become: true 110 | 111 | - name: Grab Jenkins Web UI Content 112 | # By default, Jenkins 2 is set to require auth, so the only page we can grab 113 | # without getting a 403 and redirect is the login page. 114 | uri: 115 | url: "http://localhost:8080/login" 116 | return_content: true 117 | register: jenkins_ui 118 | 119 | - name: Verify Jenkins Web UI Content 120 | action: fail 121 | when: "'Jenkins' not in jenkins_ui.content" 122 | 123 | - name: Verify jenkins_script Works with Security 124 | jenkins_script: 125 | url: "{{ jenkins_url_local }}" 126 | user: "{{ jenkins_dynamic_admin_username | default(omit) }}" 127 | password: "{{ jenkins_dynamic_admin_password | default(omit) }}" 128 | script: | 129 | println "Hello World!" 130 | register: script_test_1 131 | changed_when: false 132 | failed_when: "'Hello World!' not in script_test_1.output" 133 | 134 | - name: Verify jenkins_script Works with Template Lookup 135 | jenkins_script: 136 | url: "{{ jenkins_url_local }}" 137 | user: "{{ jenkins_dynamic_admin_username | default(omit) }}" 138 | password: "{{ jenkins_dynamic_admin_password | default(omit) }}" 139 | script: "{{ lookup('template', 'templates/jenkins_hello_world.groovy.j2') }}" 140 | register: script_test_2 141 | changed_when: false 142 | failed_when: "'Hello World!' not in script_test_2.output" 143 | --------------------------------------------------------------------------------