├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── jenkinsci │ │ └── plugins │ │ └── ansible │ │ ├── AbstractAnsibleBuilderDescriptor.java │ │ ├── AbstractAnsibleInvocation.java │ │ ├── AnsibleAdHocCommandBuilder.java │ │ ├── AnsibleAdHocCommandInvocation.java │ │ ├── AnsibleCommand.java │ │ ├── AnsibleInstallation.java │ │ ├── AnsibleInvocationException.java │ │ ├── AnsiblePlaybookBuilder.java │ │ ├── AnsiblePlaybookInvocation.java │ │ ├── CLIRunner.java │ │ ├── Inventory.java │ │ ├── InventoryContent.java │ │ ├── InventoryPath.java │ │ ├── Utils.java │ │ ├── jobdsl │ │ ├── AnsibleJobDslExtension.java │ │ └── context │ │ │ └── AnsibleContext.java │ │ └── workflow │ │ └── AnsiblePlaybookStep.java └── resources │ ├── index.jelly │ └── org │ └── jenkinsci │ └── plugins │ └── ansible │ ├── AnsibleAdHocCommandBuilder │ ├── config.jelly │ ├── config.properties │ ├── help-additionalParameters.html │ ├── help-colorizedOutput.html │ ├── help-command.html │ ├── help-credentialsId.html │ ├── help-forks.html │ ├── help-hostKeyChecking.html │ ├── help-hostPattern.html │ ├── help-module.html │ ├── help-sudo.html │ ├── help-sudoUser.html │ └── help-unbufferedOutput.html │ ├── AnsibleInstallation │ └── config.jelly │ ├── AnsiblePlaybookBuilder │ ├── config.jelly │ ├── config.properties │ ├── help-additionalParameters.html │ ├── help-colorizedOutput.html │ ├── help-credentialsId.html │ ├── help-forks.html │ ├── help-hostKeyChecking.html │ ├── help-limit.html │ ├── help-playbook.html │ ├── help-skippedTags.html │ ├── help-startAtTask.html │ ├── help-sudo.html │ ├── help-sudoUser.html │ ├── help-tags.html │ └── help-unbufferedOutput.html │ ├── InventoryContent │ ├── config.jelly │ └── help-dynamic.html │ ├── InventoryPath │ └── config.jelly │ └── workflow │ └── AnsiblePlaybookStep │ ├── config.jelly │ └── help.html └── test ├── java └── org │ └── jenkinsci │ └── plugins │ └── ansible │ ├── AnsibleAdHocCommandInvocationTest.java │ └── jobdsl │ ├── DslJobRule.java │ └── JobDslIntegrationTest.java └── resources └── jobdsl ├── adhoc.groovy └── playbook.groovy /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | *~ 3 | .DS_Store 4 | /work/ 5 | /.idea/ 6 | *.iml 7 | .classpath 8 | .project 9 | .settings/ 10 | .java-version -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jenkins Ansible Plugin 2 | ====================== 3 | 4 | This plugin gives the possibility to run [Ansible](http://www.ansible.com/) ad-hoc command or playbooks as a build step. 5 | 6 | Jenkins Wiki page: https://wiki.jenkins-ci.org/display/JENKINS/Ansible+Plugin 7 | 8 | [![Build Status](https://jenkins.ci.cloudbees.com/buildStatus/icon?job=plugins/ansible-plugin)](https://jenkins.ci.cloudbees.com/job/plugins/job/ansible-plugin/) 9 | 10 | ## Job DSL support 11 | 12 | ```groovy 13 | steps { 14 | ansiblePlaybook(String playbook) { 15 | inventoryPath(String path) 16 | inventoryContent(String content, boolean dynamic = false) 17 | ansibleName(String name) 18 | limit(String limit) 19 | tags(String tags) 20 | skippedTags(String tags) 21 | startAtTask(String task) 22 | credentialsId(String id) 23 | sudo(boolean sudo = true) 24 | sudoUser(String user = 'root') 25 | forks(int forks = 5) 26 | unbufferedOutput(boolean unbufferedOutput = true) 27 | colorizedOutput(boolean colorizedOutput = false) 28 | hostKeyChecking(boolean hostKeyChecking = false) 29 | additionalParameters(String params) 30 | } 31 | 32 | ansibleAdHoc(String module, String command) { 33 | ansibleName(String name) 34 | inventoryPath(String path) 35 | inventoryContent(String content, boolean dynamic = false) 36 | credentialsId(String id) 37 | hostPattern(String pattern) 38 | sudo(boolean sudo = true) 39 | sudoUser(String user = 'root') 40 | forks(int forks = 5) 41 | unbufferedOutput(boolean unbufferedOutput = true) 42 | colorizedOutput(boolean colorizedOutput = false) 43 | hostKeyChecking(boolean hostKeyChecking = false) 44 | additionalParameters(String params) 45 | } 46 | } 47 | ``` 48 | 49 | ### Example 50 | 51 | ```groovy 52 | steps { 53 | ansiblePlaybook('path/playbook.yml') { 54 | inventoryPath('hosts.ini') 55 | ansibleName('1.9.4') 56 | tags('one,two') 57 | credentialsId('credsid') 58 | sudo(true) 59 | sudoUser("user") 60 | } 61 | } 62 | ``` 63 | 64 | ## Workflow support 65 | 66 | Ansible playbooks can be executed from workflow scripts. Only the `playbook` parameter is mandatory. 67 | 68 | ### Example 69 | 70 | ```groovy 71 | node { 72 | ansiblePlaybook( 73 | playbook: 'path/to/playbook.yml', 74 | inventory: 'path/to/inventory.ini', 75 | credentialsId: 'sample-ssh-key', 76 | extras: '-e parameter="some value"') 77 | } 78 | ``` -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | org.jenkins-ci.plugins 5 | plugin 6 | 1.580.1 7 | 8 | 9 | org.jenkins-ci.plugins 10 | ansible 11 | 0.5-SNAPSHOT 12 | hpi 13 | Jenkins Ansible plugin 14 | Ansible support in Jenkins 15 | https://wiki.jenkins-ci.org/display/JENKINS/Ansible+Plugin 16 | 17 | 18 | 19 | Apache License, Version 2.0 20 | http://www.apache.org/licenses/LICENSE-2.0.txt 21 | repo 22 | 23 | 24 | 25 | 26 | 27 | jcsirot 28 | Jean-Christophe Sirot 29 | sirot@chelonix.com 30 | 31 | 32 | 33 | 34 | scm:git:ssh://github.com/jenkinsci/ansible-plugin.git 35 | scm:git:ssh://git@github.com/jenkinsci/ansible-plugin.git 36 | https://github.com/jcsirot/ansible-plugin 37 | HEAD 38 | 39 | 40 | 41 | 42 | repo.jenkins-ci.org 43 | http://repo.jenkins-ci.org/public/ 44 | 45 | 46 | 47 | 48 | repo.jenkins-ci.org 49 | http://repo.jenkins-ci.org/public/ 50 | 51 | 52 | 53 | 54 | 55 | org.jenkins-ci.plugins 56 | credentials 57 | ${credentials.version} 58 | 59 | 60 | org.jenkins-ci.plugins 61 | ssh-credentials 62 | ${ssh-credentials.version} 63 | 64 | 65 | org.jenkins-ci.plugins.workflow 66 | workflow-step-api 67 | ${workflow-step-api.version} 68 | true 69 | 70 | 71 | 72 | 73 | org.jenkins-ci.plugins 74 | job-dsl 75 | 1.36 76 | true 77 | 78 | 79 | 80 | 81 | junit 82 | junit 83 | ${junit.version} 84 | test 85 | 86 | 87 | org.assertj 88 | assertj-core 89 | ${assertj.version} 90 | 91 | 92 | org.mockito 93 | mockito-all 94 | ${mockito.version} 95 | test 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-compiler-plugin 105 | ${maven-compiler-plugin.version} 106 | 107 | ${project.build.sourceEncoding} 108 | 1.6 109 | 1.6 110 | 111 | 112 | 113 | org.apache.maven.plugins 114 | maven-resources-plugin 115 | ${maven-resources-plugin.version} 116 | 117 | ${project.build.sourceEncoding} 118 | 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-release-plugin 123 | ${maven-release-plugin.version} 124 | 125 | 126 | 127 | 128 | 129 | 130 | UTF-8 131 | 3.2 132 | 2.6 133 | 2.5.2 134 | 1.10 135 | 1.16.1 136 | 1.4.2 137 | 4.12 138 | 1.10.19 139 | 1.7.1 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AbstractAnsibleBuilderDescriptor.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible; 2 | 3 | import static com.cloudbees.plugins.credentials.CredentialsMatchers.*; 4 | 5 | import java.util.List; 6 | 7 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 8 | import com.cloudbees.plugins.credentials.CredentialsMatchers; 9 | import com.cloudbees.plugins.credentials.CredentialsProvider; 10 | import com.cloudbees.plugins.credentials.common.StandardListBoxModel; 11 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 12 | import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; 13 | import hudson.model.AbstractProject; 14 | import hudson.model.Project; 15 | import hudson.tasks.BuildStepDescriptor; 16 | import hudson.tasks.Builder; 17 | import hudson.util.FormValidation; 18 | import hudson.util.ListBoxModel; 19 | import jenkins.model.Jenkins; 20 | import org.apache.commons.lang.StringUtils; 21 | import org.jenkinsci.plugins.ansible.Inventory.InventoryDescriptor; 22 | import org.kohsuke.stapler.AncestorInPath; 23 | 24 | /** 25 | * Common descriptor for Ansible build steps 26 | */ 27 | public abstract class AbstractAnsibleBuilderDescriptor extends BuildStepDescriptor 28 | { 29 | private final String displayName; 30 | 31 | protected AbstractAnsibleBuilderDescriptor(String displayName) { 32 | this.displayName = displayName; 33 | load(); 34 | } 35 | 36 | protected FormValidation checkNotNullOrEmpty(String parameter, String errorMessage) { 37 | if (StringUtils.isNotBlank(parameter)) { 38 | return FormValidation.ok(); 39 | } else { 40 | return FormValidation.error(errorMessage); 41 | } 42 | } 43 | 44 | public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Project project) { 45 | return new StandardListBoxModel() 46 | .withEmptySelection() 47 | .withMatching(anyOf( 48 | instanceOf(SSHUserPrivateKey.class), 49 | instanceOf(UsernamePasswordCredentials.class)), 50 | CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, project)); 51 | } 52 | 53 | public List getInventories() { 54 | return Jenkins.getInstance().getDescriptorList(Inventory.class); 55 | } 56 | 57 | @Override 58 | public boolean isApplicable(Class klass) { 59 | return true; 60 | } 61 | 62 | @Override 63 | public String getDisplayName() { 64 | return displayName; 65 | } 66 | 67 | public AnsibleInstallation[] getInstallations() { 68 | return AnsibleInstallation.allInstallations(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AbstractAnsibleInvocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jean-Christophe Sirot 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jenkinsci.plugins.ansible; 17 | 18 | import java.io.IOException; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 23 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 24 | import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; 25 | import hudson.EnvVars; 26 | import hudson.FilePath; 27 | import hudson.model.AbstractBuild; 28 | import hudson.model.BuildListener; 29 | import hudson.model.Run; 30 | import hudson.model.TaskListener; 31 | import hudson.util.ArgumentListBuilder; 32 | import hudson.util.Secret; 33 | import org.apache.commons.lang.StringUtils; 34 | 35 | /** 36 | * Ansible command invocation 37 | */ 38 | abstract class AbstractAnsibleInvocation> { 39 | 40 | protected final EnvVars envVars; 41 | protected final TaskListener listener; 42 | protected final Run build; 43 | protected final Map environment = new HashMap(); 44 | 45 | protected String exe; 46 | protected int forks; 47 | protected boolean sudo; 48 | protected String sudoUser; 49 | protected StandardUsernameCredentials credentials; 50 | protected String additionalParameters; 51 | 52 | private FilePath key = null; 53 | private FilePath script = null; 54 | private Inventory inventory; 55 | private boolean copyCredentialsInWorkspace = false; 56 | private final FilePath ws; 57 | 58 | protected AbstractAnsibleInvocation(String exe, Run build, FilePath ws, TaskListener listener) 59 | throws IOException, InterruptedException, AnsibleInvocationException 60 | { 61 | this.build = build; 62 | this.ws = ws; 63 | this.envVars = build.getEnvironment(listener); 64 | this.listener = listener; 65 | this.exe = exe; 66 | if (exe == null) { 67 | throw new AnsibleInvocationException("Ansible executable not found, check your installation."); 68 | } 69 | } 70 | 71 | protected ArgumentListBuilder appendExecutable(ArgumentListBuilder args) { 72 | args.add(exe); 73 | return args; 74 | } 75 | 76 | public T setInventory(Inventory inventory) { 77 | this.inventory = inventory; 78 | return (T) this; 79 | } 80 | 81 | protected ArgumentListBuilder appendInventory(ArgumentListBuilder args) 82 | throws IOException, InterruptedException, AnsibleInvocationException 83 | { 84 | if (inventory == null) { 85 | // throw new AnsibleInvocationException( 86 | // "The inventory of hosts and groups is not defined. Check the job configuration."); 87 | return args; 88 | } 89 | inventory.addArgument(args, ws, envVars, listener); 90 | return args; 91 | } 92 | 93 | public T setForks(int forks) { 94 | this.forks = forks; 95 | return (T) this; 96 | } 97 | 98 | public ArgumentListBuilder appendForks(ArgumentListBuilder args) { 99 | args.add("-f").add(forks); 100 | return args; 101 | } 102 | 103 | public T setAdditionalParameters(String additionalParameters) { 104 | this.additionalParameters = additionalParameters; 105 | return (T) this; 106 | } 107 | 108 | public ArgumentListBuilder appendAdditionalParameters(ArgumentListBuilder args) { 109 | args.addTokenized(envVars.expand(additionalParameters)); 110 | return args; 111 | } 112 | 113 | public T setSudo(boolean sudo, String sudoUser) { 114 | this.sudo = sudo; 115 | this.sudoUser = sudoUser; 116 | return (T) this; 117 | } 118 | 119 | protected ArgumentListBuilder appendSudo(ArgumentListBuilder args) { 120 | if (sudo) { 121 | args.add("-s"); 122 | if (StringUtils.isNotBlank(sudoUser)) { 123 | args.add("-U").add(envVars.expand(sudoUser)); 124 | } 125 | } 126 | return args; 127 | } 128 | 129 | public T setCredentials(StandardUsernameCredentials credentials) { 130 | this.credentials = credentials; 131 | return (T) this; 132 | } 133 | 134 | public T setCredentials(StandardUsernameCredentials credentials, boolean copyCredentialsInWorkspace) { 135 | this.copyCredentialsInWorkspace = copyCredentialsInWorkspace; 136 | return setCredentials(credentials); 137 | } 138 | 139 | protected ArgumentListBuilder prependPasswordCredentials(ArgumentListBuilder args) { 140 | if (credentials instanceof UsernamePasswordCredentials) { 141 | UsernamePasswordCredentials passwordCredentials = (UsernamePasswordCredentials)credentials; 142 | args.add("sshpass").addMasked("-p" + Secret.toString(passwordCredentials.getPassword())); 143 | } 144 | return args; 145 | } 146 | 147 | protected ArgumentListBuilder appendCredentials(ArgumentListBuilder args) 148 | throws IOException, InterruptedException 149 | { 150 | if (credentials instanceof SSHUserPrivateKey) { 151 | SSHUserPrivateKey privateKeyCredentials = (SSHUserPrivateKey)credentials; 152 | key = Utils.createSshKeyFile(key, ws, privateKeyCredentials, copyCredentialsInWorkspace); 153 | args.add("--private-key").add(key); 154 | args.add("-u").add(privateKeyCredentials.getUsername()); 155 | if (privateKeyCredentials.getPassphrase() != null) { 156 | script = Utils.createSshAskPassFile(script, ws, privateKeyCredentials, copyCredentialsInWorkspace); 157 | environment.put("SSH_ASKPASS", script.getRemote()); 158 | // inspired from https://github.com/jenkinsci/git-client-plugin/pull/168 159 | // but does not work with MacOSX 160 | if (! environment.containsKey("DISPLAY")) { 161 | environment.put("DISPLAY", ":123.456"); 162 | } 163 | } 164 | } else if (credentials instanceof UsernamePasswordCredentials) { 165 | args.add("-u").add(credentials.getUsername()); 166 | args.add("-k"); 167 | } 168 | return args; 169 | } 170 | 171 | public T setUnbufferedOutput(boolean unbufferedOutput) { 172 | if (unbufferedOutput) { 173 | environment.put("PYTHONUNBUFFERED", "1"); 174 | } 175 | return (T) this; 176 | } 177 | 178 | public T setColorizedOutput(boolean colorizedOutput) { 179 | if (colorizedOutput) { 180 | environment.put("ANSIBLE_FORCE_COLOR", "true"); 181 | } 182 | return (T) this; 183 | } 184 | 185 | public T setHostKeyCheck(boolean hostKeyChecking) { 186 | if (! hostKeyChecking) { 187 | environment.put("ANSIBLE_HOST_KEY_CHECKING", "False"); 188 | } 189 | return (T) this; 190 | } 191 | 192 | abstract protected ArgumentListBuilder buildCommandLine() 193 | throws InterruptedException, AnsibleInvocationException, IOException; 194 | 195 | public boolean execute(CLIRunner runner) throws IOException, InterruptedException, AnsibleInvocationException { 196 | try { 197 | return runner.execute(buildCommandLine(), environment); 198 | } finally { 199 | if (inventory != null) { 200 | inventory.tearDown(listener); 201 | } 202 | Utils.deleteTempFile(key, listener); 203 | Utils.deleteTempFile(script, listener); 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jean-Christophe Sirot 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jenkinsci.plugins.ansible; 17 | 18 | import javax.annotation.Nonnull; 19 | import java.io.IOException; 20 | 21 | import com.cloudbees.plugins.credentials.CredentialsProvider; 22 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 23 | import hudson.AbortException; 24 | import hudson.Extension; 25 | import hudson.FilePath; 26 | import hudson.Launcher; 27 | import hudson.Util; 28 | import hudson.model.Computer; 29 | import hudson.model.Run; 30 | import hudson.model.TaskListener; 31 | import hudson.tasks.Builder; 32 | import hudson.util.FormValidation; 33 | import jenkins.tasks.SimpleBuildStep; 34 | import org.apache.commons.lang.StringUtils; 35 | import org.kohsuke.stapler.DataBoundConstructor; 36 | import org.kohsuke.stapler.DataBoundSetter; 37 | import org.kohsuke.stapler.QueryParameter; 38 | 39 | /** 40 | * A builder which wraps an Ansible Ad-Hoc command invocation. 41 | */ 42 | public class AnsibleAdHocCommandBuilder extends Builder implements SimpleBuildStep { 43 | 44 | public String ansibleName; 45 | 46 | // SSH settings 47 | /** 48 | * The id of the credentials to use. 49 | */ 50 | public String credentialsId = null; 51 | 52 | public final String hostPattern; 53 | 54 | /** 55 | * Path to the inventory file. 56 | */ 57 | public final Inventory inventory; 58 | 59 | public final String module; 60 | 61 | public final String command; 62 | 63 | public boolean sudo = false; 64 | 65 | public String sudoUser = "root"; 66 | 67 | public int forks = 5; 68 | 69 | public boolean unbufferedOutput = true; 70 | 71 | public boolean colorizedOutput = false; 72 | 73 | public boolean hostKeyChecking = false; 74 | 75 | public String additionalParameters = null; 76 | 77 | 78 | @Deprecated 79 | public AnsibleAdHocCommandBuilder(String ansibleName, String hostPattern, Inventory inventory, String module, 80 | String command, String credentialsId, boolean sudo, String sudoUser, int forks, 81 | boolean unbufferedOutput, boolean colorizedOutput, boolean hostKeyChecking, 82 | String additionalParameters) 83 | { 84 | this.ansibleName = ansibleName; 85 | this.hostPattern = hostPattern; 86 | this.inventory = inventory; 87 | this.module = module; 88 | this.command = command; 89 | this.credentialsId = credentialsId; 90 | this.sudo = sudo; 91 | this.sudoUser = sudoUser; 92 | this.forks = forks; 93 | this.unbufferedOutput = unbufferedOutput; 94 | this.colorizedOutput = colorizedOutput; 95 | this.hostKeyChecking = hostKeyChecking; 96 | this.additionalParameters = additionalParameters; 97 | } 98 | 99 | @DataBoundConstructor 100 | public AnsibleAdHocCommandBuilder(String hostPattern, Inventory inventory, String module, String command) { 101 | this.hostPattern = hostPattern; 102 | this.inventory = inventory; 103 | this.module = module; 104 | this.command = command; 105 | } 106 | 107 | @DataBoundSetter 108 | public void setAnsibleName(String ansibleName) { 109 | this.ansibleName = ansibleName; 110 | } 111 | 112 | @DataBoundSetter 113 | public void setCredentialsId(String credentialsId) { 114 | this.credentialsId = credentialsId; 115 | } 116 | 117 | @DataBoundSetter 118 | public void setSudo(boolean sudo) { 119 | this.sudo = sudo; 120 | } 121 | 122 | @DataBoundSetter 123 | public void setSudoUser(String sudoUser) { 124 | this.sudoUser = sudoUser; 125 | } 126 | 127 | @DataBoundSetter 128 | public void setForks(int forks) { 129 | this.forks = forks; 130 | } 131 | 132 | @DataBoundSetter 133 | public void setUnbufferedOutput(boolean unbufferedOutput) { 134 | this.unbufferedOutput = unbufferedOutput; 135 | } 136 | 137 | @DataBoundSetter 138 | public void setColorizedOutput(boolean colorizedOutput) { 139 | this.colorizedOutput = colorizedOutput; 140 | } 141 | 142 | @DataBoundSetter 143 | public void setHostKeyChecking(boolean hostKeyChecking) { 144 | this.hostKeyChecking = hostKeyChecking; 145 | } 146 | 147 | @DataBoundSetter 148 | public void setAdditionalParameters(String additionalParameters) { 149 | this.additionalParameters = additionalParameters; 150 | } 151 | 152 | @Override 153 | public void perform(@Nonnull Run run, @Nonnull FilePath ws, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException { 154 | try { 155 | CLIRunner runner = new CLIRunner(run, ws, launcher, listener); 156 | Computer computer = Computer.currentComputer(); 157 | if (computer == null) { 158 | throw new AbortException("The ansible playbook build step requires to be launched on a node"); 159 | } 160 | String exe = AnsibleInstallation.getExecutable(ansibleName, AnsibleCommand.ANSIBLE, computer.getNode(), listener, run.getEnvironment(listener)); 161 | AnsibleAdHocCommandInvocation invocation = new AnsibleAdHocCommandInvocation(exe, run, ws, listener); 162 | invocation.setHostPattern(hostPattern); 163 | invocation.setInventory(inventory); 164 | invocation.setModule(module); 165 | invocation.setModuleCommand(command); 166 | invocation.setSudo(sudo, sudoUser); 167 | invocation.setForks(forks); 168 | invocation.setCredentials(StringUtils.isNotBlank(credentialsId) ? 169 | CredentialsProvider.findCredentialById(credentialsId, StandardUsernameCredentials.class, run) : 170 | null); 171 | invocation.setAdditionalParameters(additionalParameters); 172 | invocation.setHostKeyCheck(hostKeyChecking); 173 | invocation.setUnbufferedOutput(unbufferedOutput); 174 | invocation.setColorizedOutput(colorizedOutput); 175 | if (!invocation.execute(runner)) { 176 | throw new AbortException("Ansible Ad-Hoc command execution failed"); 177 | } 178 | } catch (IOException ioe) { 179 | Util.displayIOException(ioe, listener); 180 | ioe.printStackTrace(listener.fatalError(hudson.tasks.Messages.CommandInterpreter_CommandFailed())); 181 | throw ioe; 182 | } catch (AnsibleInvocationException aie) { 183 | listener.fatalError(aie.getMessage()); 184 | throw new AbortException(aie.getMessage()); 185 | } 186 | } 187 | 188 | @Extension 189 | public static final class DescriptorImpl extends AbstractAnsibleBuilderDescriptor { 190 | 191 | public DescriptorImpl() { 192 | super("Invoke Ansible Ad-Hoc Command"); 193 | } 194 | 195 | public FormValidation doCheckHostPattern(@QueryParameter String hostPattern) { 196 | return checkNotNullOrEmpty(hostPattern, "Host pattern must not be empty"); 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandInvocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jean-Christophe Sirot 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jenkinsci.plugins.ansible; 17 | 18 | import java.io.IOException; 19 | 20 | import hudson.FilePath; 21 | import hudson.Launcher; 22 | import hudson.model.AbstractBuild; 23 | import hudson.model.BuildListener; 24 | import hudson.model.Run; 25 | import hudson.model.TaskListener; 26 | import hudson.util.ArgumentListBuilder; 27 | import org.apache.commons.lang.StringUtils; 28 | 29 | /** 30 | * Invoke the ansible command 31 | */ 32 | public class AnsibleAdHocCommandInvocation extends AbstractAnsibleInvocation { 33 | 34 | private String module; 35 | private String hostPattern; 36 | private String command; 37 | 38 | protected AnsibleAdHocCommandInvocation(String exe, AbstractBuild build, BuildListener listener) 39 | throws IOException, InterruptedException, AnsibleInvocationException 40 | { 41 | super(exe, build, build.getWorkspace(), listener); 42 | } 43 | 44 | public AnsibleAdHocCommandInvocation(String exe, Run build, FilePath ws, TaskListener listener) 45 | throws IOException, InterruptedException, AnsibleInvocationException 46 | { 47 | super(exe, build, ws, listener); 48 | } 49 | 50 | public AnsibleAdHocCommandInvocation setHostPattern(String hostPattern) { 51 | this.hostPattern = hostPattern; 52 | return this; 53 | } 54 | 55 | private ArgumentListBuilder appendHostPattern(ArgumentListBuilder args) { 56 | args.add(envVars.expand(hostPattern)); 57 | return args; 58 | } 59 | 60 | public AnsibleAdHocCommandInvocation setModule(String module) { 61 | this.module = module; 62 | return this; 63 | } 64 | 65 | private ArgumentListBuilder appendHModule(ArgumentListBuilder args) { 66 | if (StringUtils.isNotBlank(module)) { 67 | args.add("-m").add(envVars.expand(module)); 68 | } 69 | return args; 70 | } 71 | 72 | public AnsibleAdHocCommandInvocation setModuleCommand(String command) { 73 | this.command = command; 74 | return this; 75 | } 76 | 77 | public ArgumentListBuilder appendModuleCommand(ArgumentListBuilder args) { 78 | if (StringUtils.isNotBlank(command)) { 79 | args.add("-a").add(envVars.expand(command)); 80 | } 81 | return args; 82 | } 83 | 84 | @Override 85 | protected ArgumentListBuilder buildCommandLine() 86 | throws InterruptedException, AnsibleInvocationException, IOException 87 | { 88 | ArgumentListBuilder args = new ArgumentListBuilder(); 89 | prependPasswordCredentials(args); 90 | appendExecutable(args); 91 | appendHostPattern(args); 92 | appendInventory(args); 93 | appendHModule(args); 94 | appendModuleCommand(args); 95 | appendSudo(args); 96 | appendForks(args); 97 | appendCredentials(args); 98 | appendAdditionalParameters(args); 99 | return args; 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AnsibleCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jean-Christophe Sirot 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jenkinsci.plugins.ansible; 17 | 18 | /** 19 | * The command to be launched 20 | */ 21 | public enum AnsibleCommand { 22 | ANSIBLE("ansible"), ANSIBLE_PLAYBOOK("ansible-playbook"); 23 | 24 | private final String name; 25 | 26 | private AnsibleCommand(String name) { 27 | this.name = name; 28 | } 29 | 30 | public String getName() { 31 | return name; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AnsibleInstallation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jean-Christophe Sirot 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jenkinsci.plugins.ansible; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.io.Serializable; 21 | import java.util.List; 22 | 23 | import hudson.EnvVars; 24 | import hudson.Extension; 25 | import hudson.FilePath; 26 | import hudson.Launcher; 27 | import hudson.Util; 28 | import hudson.model.EnvironmentSpecific; 29 | import hudson.model.Node; 30 | import hudson.model.TaskListener; 31 | import hudson.remoting.Callable; 32 | import hudson.slaves.NodeSpecific; 33 | import hudson.tools.ToolDescriptor; 34 | import hudson.tools.ToolInstallation; 35 | import hudson.tools.ToolProperty; 36 | import jenkins.model.Jenkins; 37 | import net.sf.json.JSONObject; 38 | import org.jenkinsci.remoting.RoleChecker; 39 | import org.kohsuke.stapler.DataBoundConstructor; 40 | import org.kohsuke.stapler.StaplerRequest; 41 | 42 | /** 43 | * {@code ToolInstallation} for Ansible 44 | */ 45 | public class AnsibleInstallation extends ToolInstallation 46 | implements EnvironmentSpecific, NodeSpecific, Serializable { 47 | 48 | @DataBoundConstructor 49 | public AnsibleInstallation(String name, String home, List> properties) { 50 | super(name, home, properties); 51 | } 52 | 53 | public AnsibleInstallation forEnvironment(EnvVars environment) { 54 | return new AnsibleInstallation(getName(), environment.expand(getHome()), getProperties().toList()); 55 | } 56 | 57 | public AnsibleInstallation forNode(Node node, TaskListener log) throws IOException, InterruptedException { 58 | return new AnsibleInstallation(getName(), translateFor(node, log), getProperties().toList()); 59 | } 60 | 61 | public static String getExecutable(String name, AnsibleCommand command, Node node, TaskListener listener, EnvVars env) throws IOException, InterruptedException { 62 | if (name != null) { 63 | Jenkins j = Jenkins.getInstance(); 64 | if (j != null) { 65 | for (AnsibleInstallation tool : j.getDescriptorByType(DescriptorImpl.class).getInstallations()) { 66 | if (tool.getName().equals(name)) { 67 | if (node != null) { 68 | tool = tool.forNode(node, listener); 69 | } 70 | if (env != null) { 71 | tool = tool.forEnvironment(env); 72 | } 73 | String home = Util.fixEmpty(tool.getHome()); 74 | if (home != null) { 75 | if (node != null) { 76 | FilePath homePath = node.createPath(home); 77 | if (homePath != null) { 78 | return homePath.child(command.getName()).getRemote(); 79 | } 80 | } 81 | return home + "/" + command.getName(); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | return command.getName(); 88 | } 89 | 90 | public static AnsibleInstallation[] allInstallations() { 91 | AnsibleInstallation.DescriptorImpl ansibleDescriptor = Jenkins.getInstance().getDescriptorByType(AnsibleInstallation.DescriptorImpl.class); 92 | return ansibleDescriptor.getInstallations(); 93 | } 94 | 95 | public static AnsibleInstallation getInstallation(String ansibleInstallation) throws IOException { 96 | AnsibleInstallation[] installations = allInstallations(); 97 | if (ansibleInstallation == null) { 98 | if (installations.length == 0) { 99 | throw new IOException("Ansible not found"); 100 | } 101 | return installations[0]; 102 | } else { 103 | for (AnsibleInstallation installation: installations) { 104 | if (ansibleInstallation.equals(installation.getName())) { 105 | return installation; 106 | } 107 | } 108 | } 109 | throw new IOException("Ansible not found"); 110 | } 111 | 112 | @Override 113 | public void buildEnvVars(EnvVars env) { 114 | String home = Util.fixEmpty(getHome()); 115 | if (home != null) { 116 | env.put("PATH+ANSIBLE", home); 117 | } 118 | } 119 | 120 | @Extension 121 | public static class DescriptorImpl extends ToolDescriptor { 122 | 123 | public DescriptorImpl() { 124 | load(); 125 | } 126 | 127 | @Override 128 | public boolean configure(StaplerRequest req, JSONObject json) throws FormException { 129 | super.configure(req, json); 130 | save(); 131 | return true; 132 | } 133 | 134 | @Override 135 | public String getDisplayName() { 136 | return "Ansible"; 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AnsibleInvocationException.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible; 2 | 3 | /** 4 | * Created with IntelliJ IDEA. 5 | * User: sirot 6 | * Date: 04/05/15 7 | * Time: 23:30 8 | * To change this template use File | Settings | File Templates. 9 | */ 10 | public class AnsibleInvocationException extends Exception { 11 | 12 | public AnsibleInvocationException(String message) { 13 | super(message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jean-Christophe Sirot 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jenkinsci.plugins.ansible; 17 | 18 | import javax.annotation.Nonnull; 19 | import java.io.IOException; 20 | 21 | import com.cloudbees.plugins.credentials.CredentialsProvider; 22 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 23 | import hudson.AbortException; 24 | import hudson.EnvVars; 25 | import hudson.Extension; 26 | import hudson.FilePath; 27 | import hudson.Launcher; 28 | import hudson.Util; 29 | import hudson.model.AbstractBuild; 30 | import hudson.model.BuildListener; 31 | import hudson.model.Computer; 32 | import hudson.model.Node; 33 | import hudson.model.Run; 34 | import hudson.model.TaskListener; 35 | import hudson.tasks.BuildStepMonitor; 36 | import hudson.tasks.Builder; 37 | import hudson.util.FormValidation; 38 | import jenkins.tasks.SimpleBuildStep; 39 | import org.apache.commons.lang.StringUtils; 40 | import org.kohsuke.stapler.DataBoundConstructor; 41 | import org.kohsuke.stapler.DataBoundSetter; 42 | import org.kohsuke.stapler.QueryParameter; 43 | 44 | /** 45 | * A builder which wraps an Ansible playbook invocation. 46 | */ 47 | public class AnsiblePlaybookBuilder extends Builder implements SimpleBuildStep 48 | { 49 | 50 | public final String playbook; 51 | 52 | public final Inventory inventory; 53 | 54 | public String ansibleName = null; 55 | 56 | public String limit = null; 57 | 58 | public String tags = null; 59 | 60 | public String skippedTags = null; 61 | 62 | public String startAtTask = null; 63 | 64 | /** 65 | * The id of the credentials to use. 66 | */ 67 | public String credentialsId = null; 68 | 69 | public boolean sudo = false; 70 | 71 | public String sudoUser = "root"; 72 | 73 | public int forks = 5; 74 | 75 | public boolean unbufferedOutput = true; 76 | 77 | public boolean colorizedOutput = false; 78 | 79 | public boolean hostKeyChecking = false; 80 | 81 | public String additionalParameters = null; 82 | 83 | public boolean copyCredentialsInWorkspace = false; 84 | 85 | @Deprecated 86 | public AnsiblePlaybookBuilder(String ansibleName, String playbook, Inventory inventory, String limit, String tags, 87 | String skippedTags, String startAtTask, String credentialsId, boolean sudo, 88 | String sudoUser, int forks, boolean unbufferedOutput, boolean colorizedOutput, 89 | boolean hostKeyChecking, String additionalParameters) 90 | { 91 | this.ansibleName = ansibleName; 92 | this.playbook = playbook; 93 | this.inventory = inventory; 94 | this.limit = limit; 95 | this.tags = tags; 96 | this.skippedTags = skippedTags; 97 | this.startAtTask = startAtTask; 98 | this.credentialsId = credentialsId; 99 | this.sudo = sudo; 100 | this.sudoUser = sudoUser; 101 | this.forks = forks; 102 | this.unbufferedOutput = unbufferedOutput; 103 | this.colorizedOutput = colorizedOutput; 104 | this.hostKeyChecking = hostKeyChecking; 105 | this.additionalParameters = additionalParameters; 106 | } 107 | 108 | @DataBoundConstructor 109 | public AnsiblePlaybookBuilder(String playbook, Inventory inventory) { 110 | this.playbook = playbook; 111 | this.inventory = inventory; 112 | } 113 | 114 | @DataBoundSetter 115 | public void setAnsibleName(String ansibleName) { 116 | this.ansibleName = ansibleName; 117 | } 118 | 119 | @DataBoundSetter 120 | public void setLimit(String limit) { 121 | this.limit = limit; 122 | } 123 | 124 | @DataBoundSetter 125 | public void setTags(String tags) { 126 | this.tags = tags; 127 | } 128 | 129 | @DataBoundSetter 130 | public void setSkippedTags(String skippedTags) { 131 | this.skippedTags = skippedTags; 132 | } 133 | 134 | @DataBoundSetter 135 | public void setStartAtTask(String startAtTask) { 136 | this.startAtTask = startAtTask; 137 | } 138 | 139 | @DataBoundSetter 140 | public void setCredentialsId(String credentialsId) { 141 | setCredentialsId(credentialsId, false); 142 | } 143 | 144 | public void setCredentialsId(String credentialsId, boolean copyCredentialsInWorkspace) { 145 | this.credentialsId = credentialsId; 146 | this.copyCredentialsInWorkspace = copyCredentialsInWorkspace; 147 | } 148 | 149 | @DataBoundSetter 150 | public void setSudo(boolean sudo) { 151 | this.sudo = sudo; 152 | } 153 | 154 | @DataBoundSetter 155 | public void setSudoUser(String sudoUser) { 156 | this.sudoUser = sudoUser; 157 | } 158 | 159 | @DataBoundSetter 160 | public void setForks(int forks) { 161 | this.forks = forks; 162 | } 163 | 164 | @DataBoundSetter 165 | public void setUnbufferedOutput(boolean unbufferedOutput) { 166 | this.unbufferedOutput = unbufferedOutput; 167 | } 168 | 169 | @DataBoundSetter 170 | public void setColorizedOutput(boolean colorizedOutput) { 171 | this.colorizedOutput = colorizedOutput; 172 | } 173 | 174 | @DataBoundSetter 175 | public void setHostKeyChecking(boolean hostKeyChecking) { 176 | this.hostKeyChecking = hostKeyChecking; 177 | } 178 | 179 | @DataBoundSetter 180 | public void setAdditionalParameters(String additionalParameters) { 181 | this.additionalParameters = additionalParameters; 182 | } 183 | 184 | @Override 185 | public void perform(@Nonnull Run run, @Nonnull FilePath ws, @Nonnull Launcher launcher, @Nonnull TaskListener listener) 186 | throws InterruptedException, IOException 187 | { 188 | Computer computer = Computer.currentComputer(); 189 | if (computer == null) { 190 | throw new AbortException("The ansible playbook build step requires to be launched on a node"); 191 | } 192 | perform(run, computer.getNode(), ws, launcher, listener, run.getEnvironment(listener)); 193 | } 194 | 195 | public void perform(@Nonnull Run run, @Nonnull Node node, @Nonnull FilePath ws, @Nonnull Launcher launcher, @Nonnull TaskListener listener, EnvVars envVars) 196 | throws InterruptedException, IOException 197 | { 198 | try { 199 | CLIRunner runner = new CLIRunner(run, ws, launcher, listener); 200 | String exe = AnsibleInstallation.getExecutable(ansibleName, AnsibleCommand.ANSIBLE_PLAYBOOK, node, listener, envVars); 201 | AnsiblePlaybookInvocation invocation = new AnsiblePlaybookInvocation(exe, run, ws, listener); 202 | invocation.setPlaybook(playbook); 203 | invocation.setInventory(inventory); 204 | invocation.setLimit(limit); 205 | invocation.setTags(tags); 206 | invocation.setSkippedTags(skippedTags); 207 | invocation.setStartTask(startAtTask); 208 | invocation.setSudo(sudo, sudoUser); 209 | invocation.setForks(forks); 210 | invocation.setCredentials(StringUtils.isNotBlank(credentialsId) ? 211 | CredentialsProvider.findCredentialById(credentialsId, StandardUsernameCredentials.class, run) : null, 212 | copyCredentialsInWorkspace); 213 | invocation.setAdditionalParameters(additionalParameters); 214 | invocation.setHostKeyCheck(hostKeyChecking); 215 | invocation.setUnbufferedOutput(unbufferedOutput); 216 | invocation.setColorizedOutput(colorizedOutput); 217 | if (!invocation.execute(runner)) { 218 | throw new AbortException("Ansible playbook execution failed"); 219 | } 220 | } catch (IOException ioe) { 221 | Util.displayIOException(ioe, listener); 222 | ioe.printStackTrace(listener.fatalError(hudson.tasks.Messages.CommandInterpreter_CommandFailed())); 223 | throw ioe; 224 | } catch (AnsibleInvocationException aie) { 225 | listener.fatalError(aie.getMessage()); 226 | throw new AbortException(aie.getMessage()); 227 | } 228 | } 229 | 230 | @Override 231 | public BuildStepMonitor getRequiredMonitorService() { 232 | return BuildStepMonitor.NONE; 233 | } 234 | 235 | @Extension 236 | public static final class DescriptorImpl extends AbstractAnsibleBuilderDescriptor 237 | { 238 | public DescriptorImpl() { 239 | super("Invoke Ansible Playbook"); 240 | } 241 | 242 | public FormValidation doCheckPlaybook(@QueryParameter String playbook) { 243 | return checkNotNullOrEmpty(playbook, "Path to playbook must not be empty"); 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AnsiblePlaybookInvocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jean-Christophe Sirot 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jenkinsci.plugins.ansible; 17 | 18 | import java.io.IOException; 19 | 20 | import hudson.FilePath; 21 | import hudson.Launcher; 22 | import hudson.model.AbstractBuild; 23 | import hudson.model.BuildListener; 24 | import hudson.model.Run; 25 | import hudson.model.TaskListener; 26 | import hudson.util.ArgumentListBuilder; 27 | import org.apache.commons.lang.StringUtils; 28 | 29 | /** 30 | * Invoke the ansible-playbook command 31 | */ 32 | public class AnsiblePlaybookInvocation extends AbstractAnsibleInvocation { 33 | 34 | private String playbook; 35 | private String limit; 36 | private String tags; 37 | private String skippedTags; 38 | private String startAtTask; 39 | 40 | protected AnsiblePlaybookInvocation(String exe, AbstractBuild build, BuildListener listener) 41 | throws IOException, InterruptedException, AnsibleInvocationException 42 | { 43 | this(exe, build, build.getWorkspace(), listener); 44 | } 45 | 46 | public AnsiblePlaybookInvocation(String exe, Run build, FilePath ws, TaskListener listener) 47 | throws IOException, InterruptedException, AnsibleInvocationException 48 | { 49 | super(exe, build, ws, listener); 50 | } 51 | 52 | public AnsiblePlaybookInvocation setPlaybook(String playbook) { 53 | this.playbook = playbook; 54 | return this; 55 | } 56 | 57 | private ArgumentListBuilder appendPlaybook(ArgumentListBuilder args) { 58 | args.add(envVars.expand(playbook)); 59 | return args; 60 | } 61 | 62 | public AnsiblePlaybookInvocation setLimit(String limit) { 63 | this.limit = limit; 64 | return this; 65 | } 66 | 67 | private ArgumentListBuilder appendLimit(ArgumentListBuilder args) { 68 | if (StringUtils.isNotBlank(limit)) { 69 | args.add("-l").add(envVars.expand(limit)); 70 | } 71 | return args; 72 | } 73 | 74 | public AnsiblePlaybookInvocation setTags(String tags) { 75 | this.tags = tags; 76 | return this; 77 | } 78 | 79 | private ArgumentListBuilder appendTags(ArgumentListBuilder args) { 80 | if (StringUtils.isNotBlank(tags)) { 81 | args.add("-t").add(envVars.expand(tags)); 82 | } 83 | return args; 84 | } 85 | 86 | public AnsiblePlaybookInvocation setSkippedTags(String skippedTags) { 87 | this.skippedTags = skippedTags; 88 | return this; 89 | } 90 | 91 | private ArgumentListBuilder appendSkippedTags(ArgumentListBuilder args) { 92 | if (StringUtils.isNotBlank(skippedTags)) { 93 | args.addKeyValuePair("", "--skip-tags", envVars.expand(skippedTags), false); 94 | } 95 | return args; 96 | } 97 | 98 | public AnsiblePlaybookInvocation setStartTask(String startAtTask) { 99 | this.startAtTask = startAtTask; 100 | return this; 101 | } 102 | 103 | private ArgumentListBuilder appendStartTask(ArgumentListBuilder args) { 104 | if (StringUtils.isNotBlank(startAtTask)) { 105 | args.addKeyValuePair("", "--start-at-task", envVars.expand(startAtTask), false); 106 | } 107 | return args; 108 | } 109 | 110 | @Override 111 | protected ArgumentListBuilder buildCommandLine() throws InterruptedException, AnsibleInvocationException, IOException { 112 | ArgumentListBuilder args = new ArgumentListBuilder(); 113 | prependPasswordCredentials(args); 114 | appendExecutable(args); 115 | appendPlaybook(args); 116 | appendInventory(args); 117 | appendLimit(args); 118 | appendTags(args); 119 | appendSkippedTags(args); 120 | appendStartTask(args); 121 | appendSudo(args); 122 | appendForks(args); 123 | appendCredentials(args); 124 | appendAdditionalParameters(args); 125 | return args; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/CLIRunner.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible; 2 | 3 | import java.io.IOException; 4 | import java.util.Map; 5 | 6 | import hudson.FilePath; 7 | import hudson.Launcher; 8 | import hudson.model.AbstractBuild; 9 | import hudson.model.BuildListener; 10 | import hudson.model.Run; 11 | import hudson.model.TaskListener; 12 | import hudson.util.ArgumentListBuilder; 13 | 14 | /** 15 | * Created with IntelliJ IDEA. 16 | * User: sirot 17 | * Date: 23/05/15 18 | * Time: 22:56 19 | * To change this template use File | Settings | File Templates. 20 | */ 21 | public class CLIRunner 22 | { 23 | private final Launcher launcher; 24 | private final Run build; 25 | private final TaskListener listener; 26 | private final FilePath ws; 27 | 28 | public CLIRunner(AbstractBuild build, Launcher launcher, BuildListener listener) { 29 | this.launcher = launcher; 30 | this.build = build; 31 | this.listener = listener; 32 | this.ws = build.getWorkspace(); 33 | } 34 | 35 | public CLIRunner(Run build, FilePath ws, Launcher launcher, TaskListener listener) { 36 | this.launcher = launcher; 37 | this.build = build; 38 | this.listener = listener; 39 | this.ws = ws; 40 | } 41 | 42 | public boolean execute(ArgumentListBuilder args, Map environment) 43 | throws IOException, InterruptedException 44 | { 45 | return launcher.launch() 46 | .pwd(ws) 47 | .envs(environment) 48 | .cmds(args) 49 | .stdout(listener).join() == 0; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/Inventory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jean-Christophe Sirot 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jenkinsci.plugins.ansible; 17 | 18 | import java.io.IOException; 19 | 20 | import hudson.EnvVars; 21 | import hudson.FilePath; 22 | import hudson.model.BuildListener; 23 | import hudson.model.Describable; 24 | import hudson.model.Descriptor; 25 | import hudson.model.TaskListener; 26 | import hudson.util.ArgumentListBuilder; 27 | import jenkins.model.Jenkins; 28 | 29 | /** 30 | * Common Ansible inventory. 31 | */ 32 | public abstract class Inventory implements Describable 33 | { 34 | /** 35 | * @return 36 | * @see hudson.model.Describable#getDescriptor() 37 | */ 38 | @SuppressWarnings("unchecked") 39 | public Descriptor getDescriptor() { 40 | return Jenkins.getInstance().getDescriptorOrDie(getClass()); 41 | } 42 | 43 | protected abstract InventoryHandler getHandler(); 44 | 45 | public void addArgument(ArgumentListBuilder args, FilePath workspace, EnvVars envVars, TaskListener listener) 46 | throws InterruptedException, IOException 47 | { 48 | getHandler().addArgument(args, workspace, envVars, listener); 49 | } 50 | 51 | public void tearDown(TaskListener listener) throws InterruptedException, IOException { 52 | getHandler().tearDown(listener); 53 | } 54 | 55 | public abstract static class InventoryDescriptor extends Descriptor { } 56 | 57 | protected static interface InventoryHandler { 58 | 59 | void addArgument(ArgumentListBuilder args, FilePath workspace, EnvVars envVars, TaskListener listener) 60 | throws InterruptedException, IOException; 61 | 62 | void tearDown(TaskListener listener) throws InterruptedException, IOException; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/InventoryContent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jean-Christophe Sirot 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jenkinsci.plugins.ansible; 17 | 18 | import java.io.IOException; 19 | 20 | import hudson.EnvVars; 21 | import hudson.Extension; 22 | import hudson.FilePath; 23 | import hudson.model.BuildListener; 24 | import hudson.model.TaskListener; 25 | import hudson.util.ArgumentListBuilder; 26 | import org.kohsuke.stapler.DataBoundConstructor; 27 | 28 | /** 29 | * Inline content for Ansible inventory. Inventory may be dynamic or not. 30 | */ 31 | public class InventoryContent extends Inventory 32 | { 33 | public final String content; 34 | public final boolean dynamic; 35 | 36 | private transient FilePath inventory = null; 37 | 38 | @DataBoundConstructor 39 | public InventoryContent(String content, boolean dynamic) { 40 | this.content = content; 41 | this.dynamic = dynamic; 42 | } 43 | 44 | @Override 45 | protected InventoryHandler getHandler() 46 | { 47 | return new InventoryHandler() { 48 | public void addArgument(ArgumentListBuilder args, FilePath workspace, EnvVars envVars, TaskListener listener) 49 | throws InterruptedException, IOException 50 | { 51 | inventory = createInventoryFile(inventory, workspace, envVars.expand(content)); 52 | args.add("-i").add(inventory); 53 | } 54 | 55 | public void tearDown(TaskListener listener) throws InterruptedException, IOException { 56 | Utils.deleteTempFile(inventory, listener); 57 | } 58 | 59 | private FilePath createInventoryFile(FilePath inventory, FilePath workspace, String content) throws IOException, InterruptedException { 60 | inventory = workspace.createTextTempFile("inventory", ".ini", content, false); 61 | inventory.chmod(dynamic ? 0500 : 0400); 62 | return inventory; 63 | } 64 | }; 65 | } 66 | 67 | @Extension 68 | public static class DescriptorImpl extends InventoryDescriptor { 69 | 70 | @Override 71 | public String getDisplayName() { 72 | return "Inline content"; 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/InventoryPath.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jean-Christophe Sirot 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jenkinsci.plugins.ansible; 17 | 18 | import hudson.EnvVars; 19 | import hudson.Extension; 20 | import hudson.FilePath; 21 | import hudson.model.BuildListener; 22 | import hudson.model.TaskListener; 23 | import hudson.util.ArgumentListBuilder; 24 | import org.kohsuke.stapler.DataBoundConstructor; 25 | 26 | /** 27 | * Path to a file containing an Ansible inventory 28 | */ 29 | public class InventoryPath extends Inventory 30 | { 31 | public final String path; 32 | 33 | @DataBoundConstructor 34 | public InventoryPath(String path) { 35 | this.path = path; 36 | } 37 | 38 | @Override 39 | protected InventoryHandler getHandler() 40 | { 41 | return new InventoryHandler() 42 | { 43 | public void addArgument(ArgumentListBuilder args, FilePath workspace, EnvVars envVars, TaskListener listener) 44 | { 45 | String expandedPath = envVars.expand(InventoryPath.this.path); 46 | args.add("-i").add(expandedPath); 47 | } 48 | 49 | public void tearDown(TaskListener listener) 50 | { 51 | } 52 | }; 53 | } 54 | 55 | @Extension 56 | public static class DescriptorImpl extends InventoryDescriptor { 57 | 58 | @Override 59 | public String getDisplayName() { 60 | return "File"; 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jean-Christophe Sirot 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jenkinsci.plugins.ansible; 17 | 18 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 19 | import hudson.FilePath; 20 | import hudson.model.TaskListener; 21 | import hudson.util.Secret; 22 | 23 | import java.io.IOException; 24 | import java.util.List; 25 | 26 | class Utils 27 | { 28 | /** 29 | * Copy the SSH private key into a temporary file. 30 | * 31 | * @param key the destination file 32 | * @param credentials the SSH key 33 | * @return the file 34 | * @throws IOException 35 | * @throws InterruptedException 36 | */ 37 | static FilePath createSshKeyFile(FilePath key, FilePath workspace, SSHUserPrivateKey credentials, boolean inWorkspace) throws IOException, InterruptedException { 38 | StringBuilder sb = new StringBuilder(); 39 | List privateKeys = credentials.getPrivateKeys(); 40 | for (String s : privateKeys) { 41 | sb.append(s); 42 | } 43 | key = workspace.createTextTempFile("ssh", ".key", sb.toString(), inWorkspace); 44 | key.chmod(0400); 45 | return key; 46 | } 47 | 48 | static FilePath createSshAskPassFile(FilePath script, FilePath workspace, SSHUserPrivateKey credentials, boolean inWorkspace) throws IOException, InterruptedException { 49 | StringBuilder sb = new StringBuilder(); 50 | sb.append("#! /bin/sh\n").append("/bin/echo \"" + Secret.toString(credentials.getPassphrase()) + "\""); 51 | script = workspace.createTextTempFile("ssh", ".sh", sb.toString(), inWorkspace); 52 | script.chmod(0700); 53 | return script; 54 | } 55 | 56 | /** 57 | * Delete a temporary file. Print a warning in the log when deletion fails. 58 | * 59 | * @param tempFile the file to be removed 60 | * @param listener the build listener 61 | */ 62 | static void deleteTempFile(FilePath tempFile, TaskListener listener) throws IOException, InterruptedException { 63 | if (tempFile != null) { 64 | try { 65 | tempFile.delete(); 66 | } catch (IOException ioe) { 67 | if (tempFile.exists()) { 68 | listener.getLogger().println("[WARNING] temp file " + tempFile + " not deleted"); 69 | } 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/jobdsl/AnsibleJobDslExtension.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible.jobdsl; 2 | 3 | import hudson.Extension; 4 | import javaposse.jobdsl.dsl.helpers.step.StepContext; 5 | import javaposse.jobdsl.plugin.ContextExtensionPoint; 6 | import javaposse.jobdsl.plugin.DslExtensionMethod; 7 | import org.jenkinsci.plugins.ansible.AnsibleAdHocCommandBuilder; 8 | import org.jenkinsci.plugins.ansible.AnsiblePlaybookBuilder; 9 | import org.jenkinsci.plugins.ansible.jobdsl.context.AnsibleContext; 10 | 11 | /** 12 | * @author lanwen (Merkushev Kirill) 13 | */ 14 | @Extension(optional = true) 15 | public class AnsibleJobDslExtension extends ContextExtensionPoint { 16 | 17 | @DslExtensionMethod(context = StepContext.class) 18 | public Object ansibleAdHoc(String module, String command, Runnable closure) { 19 | AnsibleContext context = new AnsibleContext(); 20 | executeInContext(closure, context); 21 | 22 | AnsibleAdHocCommandBuilder adhoc = new AnsibleAdHocCommandBuilder( 23 | context.getHostPattern(), context.getInventory(), module, command 24 | ); 25 | 26 | adhoc.setAdditionalParameters(context.getAdditionalParameters()); 27 | adhoc.setAnsibleName(context.getAnsibleName()); 28 | adhoc.setCredentialsId(context.getCredentialsId()); 29 | adhoc.setColorizedOutput(context.isColorizedOutput()); 30 | adhoc.setForks(context.getForks()); 31 | adhoc.setHostKeyChecking(context.isHostKeyChecking()); 32 | adhoc.setSudo(context.isSudo()); 33 | adhoc.setSudoUser(context.getSudoUser()); 34 | adhoc.setUnbufferedOutput(context.isUnbufferedOutput()); 35 | 36 | return adhoc; 37 | } 38 | 39 | @DslExtensionMethod(context = StepContext.class) 40 | public Object ansiblePlaybook(String playbook, Runnable closure) { 41 | AnsibleContext context = new AnsibleContext(); 42 | executeInContext(closure, context); 43 | 44 | AnsiblePlaybookBuilder plbook = new AnsiblePlaybookBuilder(playbook, context.getInventory()); 45 | 46 | plbook.setAdditionalParameters(context.getAdditionalParameters()); 47 | plbook.setAnsibleName(context.getAnsibleName()); 48 | plbook.setCredentialsId(context.getCredentialsId()); 49 | plbook.setColorizedOutput(context.isColorizedOutput()); 50 | plbook.setForks(context.getForks()); 51 | plbook.setHostKeyChecking(context.isHostKeyChecking()); 52 | plbook.setSudo(context.isSudo()); 53 | plbook.setSudoUser(context.getSudoUser()); 54 | plbook.setUnbufferedOutput(context.isUnbufferedOutput()); 55 | 56 | plbook.setLimit(context.getLimit()); 57 | plbook.setTags(context.getTags()); 58 | plbook.setSkippedTags(context.getSkippedTags()); 59 | plbook.setStartAtTask(context.getStartAtTask()); 60 | 61 | return plbook; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/jobdsl/context/AnsibleContext.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible.jobdsl.context; 2 | 3 | import javaposse.jobdsl.dsl.Context; 4 | import org.jenkinsci.plugins.ansible.Inventory; 5 | import org.jenkinsci.plugins.ansible.InventoryContent; 6 | import org.jenkinsci.plugins.ansible.InventoryPath; 7 | 8 | /** 9 | * @author lanwen (Merkushev Kirill) 10 | */ 11 | public class AnsibleContext implements Context { 12 | private Inventory inventory; 13 | private String ansibleName; 14 | private String credentialsId; 15 | private boolean sudo = false; 16 | private String sudoUser = "root"; 17 | private int forks = 5; 18 | private boolean unbufferedOutput = true; 19 | private boolean colorizedOutput = false; 20 | private boolean hostKeyChecking = false; 21 | private String additionalParameters; 22 | 23 | /* adhoc-only */ 24 | 25 | private String hostPattern; 26 | 27 | /* playbook-only */ 28 | 29 | public String limit; 30 | public String tags; 31 | public String skippedTags; 32 | public String startAtTask; 33 | 34 | public void inventoryContent(String content, boolean dynamic) { 35 | this.inventory = new InventoryContent(content, dynamic); 36 | } 37 | 38 | public void inventoryContent(String content) { 39 | this.inventory = new InventoryContent(content, false); 40 | } 41 | 42 | public void inventoryPath(String path) { 43 | this.inventory = new InventoryPath(path); 44 | } 45 | 46 | public void ansibleName(String ansibleName) { 47 | this.ansibleName = ansibleName; 48 | } 49 | 50 | public void credentialsId(String credentialsId) { 51 | this.credentialsId = credentialsId; 52 | } 53 | 54 | public void sudo(boolean sudo) { 55 | this.sudo = sudo; 56 | } 57 | 58 | public void sudoUser(String sudoUser) { 59 | this.sudoUser = sudoUser; 60 | } 61 | 62 | public void forks(int forks) { 63 | this.forks = forks; 64 | } 65 | 66 | public void unbufferedOutput(boolean unbufferedOutput) { 67 | this.unbufferedOutput = unbufferedOutput; 68 | } 69 | 70 | public void colorizedOutput(boolean colorizedOutput) { 71 | this.colorizedOutput = colorizedOutput; 72 | } 73 | 74 | public void hostKeyChecking(boolean hostKeyChecking) { 75 | this.hostKeyChecking = hostKeyChecking; 76 | } 77 | 78 | public void additionalParameters(String additionalParameters) { 79 | this.additionalParameters = additionalParameters; 80 | } 81 | 82 | public void hostPattern(String hostPattern) { 83 | this.hostPattern = hostPattern; 84 | } 85 | 86 | public void limit(String limit) { 87 | this.limit = limit; 88 | } 89 | 90 | public void tags(String tags) { 91 | this.tags = tags; 92 | } 93 | 94 | public void skippedTags(String skippedTags) { 95 | this.skippedTags = skippedTags; 96 | } 97 | 98 | public void startAtTask(String startAtTask) { 99 | this.startAtTask = startAtTask; 100 | } 101 | 102 | public String getAnsibleName() { 103 | return ansibleName; 104 | } 105 | 106 | public String getCredentialsId() { 107 | return credentialsId; 108 | } 109 | 110 | public Inventory getInventory() { 111 | return inventory; 112 | } 113 | 114 | public boolean isSudo() { 115 | return sudo; 116 | } 117 | 118 | public String getSudoUser() { 119 | return sudoUser; 120 | } 121 | 122 | public int getForks() { 123 | return forks; 124 | } 125 | 126 | public boolean isUnbufferedOutput() { 127 | return unbufferedOutput; 128 | } 129 | 130 | public boolean isColorizedOutput() { 131 | return colorizedOutput; 132 | } 133 | 134 | public boolean isHostKeyChecking() { 135 | return hostKeyChecking; 136 | } 137 | 138 | public String getAdditionalParameters() { 139 | return additionalParameters; 140 | } 141 | 142 | public String getHostPattern() { 143 | return hostPattern; 144 | } 145 | 146 | public String getLimit() { 147 | return limit; 148 | } 149 | 150 | public String getTags() { 151 | return tags; 152 | } 153 | 154 | public String getSkippedTags() { 155 | return skippedTags; 156 | } 157 | 158 | public String getStartAtTask() { 159 | return startAtTask; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/workflow/AnsiblePlaybookStep.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible.workflow; 2 | 3 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 4 | import com.cloudbees.plugins.credentials.CredentialsProvider; 5 | import com.cloudbees.plugins.credentials.common.StandardListBoxModel; 6 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 7 | import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; 8 | import com.google.inject.Inject; 9 | import hudson.EnvVars; 10 | import hudson.Extension; 11 | import hudson.FilePath; 12 | import hudson.Launcher; 13 | import hudson.Util; 14 | import hudson.model.Computer; 15 | import hudson.model.Project; 16 | import hudson.model.Run; 17 | import hudson.model.TaskListener; 18 | import hudson.util.ListBoxModel; 19 | import org.apache.commons.lang.StringUtils; 20 | import org.jenkinsci.plugins.ansible.AnsibleInstallation; 21 | import org.jenkinsci.plugins.ansible.AnsiblePlaybookBuilder; 22 | import org.jenkinsci.plugins.ansible.Inventory; 23 | import org.jenkinsci.plugins.ansible.InventoryPath; 24 | import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl; 25 | import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl; 26 | import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousStepExecution; 27 | import org.jenkinsci.plugins.workflow.steps.StepContextParameter; 28 | import org.kohsuke.stapler.AncestorInPath; 29 | import org.kohsuke.stapler.DataBoundConstructor; 30 | import org.kohsuke.stapler.DataBoundSetter; 31 | 32 | import static com.cloudbees.plugins.credentials.CredentialsMatchers.anyOf; 33 | import static com.cloudbees.plugins.credentials.CredentialsMatchers.instanceOf; 34 | 35 | /** 36 | * The Ansible playbook invocation step for the Jenkins workflow plugin. 37 | */ 38 | public class AnsiblePlaybookStep extends AbstractStepImpl { 39 | 40 | private final String playbook; 41 | private String inventory; 42 | private String installation; 43 | private String credentialsId; 44 | private boolean sudo = false; 45 | private String sudoUser = "root"; 46 | private String limit = null; 47 | private String tags = null; 48 | private String skippedTags = null; 49 | private String startAtTask = null; 50 | private String extras = null; 51 | 52 | @DataBoundConstructor 53 | public AnsiblePlaybookStep(String playbook) { 54 | this.playbook = playbook; 55 | } 56 | 57 | @DataBoundSetter 58 | public void setInventory(String inventory) { 59 | this.inventory = Util.fixEmptyAndTrim(inventory); 60 | } 61 | 62 | @DataBoundSetter 63 | public void setCredentialsId(String credentialsId) { 64 | this.credentialsId = Util.fixEmptyAndTrim(credentialsId); 65 | } 66 | 67 | @DataBoundSetter 68 | public void setSudo(boolean sudo) { 69 | this.sudo = sudo; 70 | } 71 | 72 | @DataBoundSetter 73 | public void setSudoUser(String sudoUser) { 74 | this.sudoUser = Util.fixEmptyAndTrim(sudoUser); 75 | } 76 | 77 | @DataBoundSetter 78 | public void setInstallation(String installation) { 79 | this.installation = Util.fixEmptyAndTrim(installation); 80 | } 81 | 82 | @DataBoundSetter 83 | public void setLimit(String limit) { 84 | this.limit = Util.fixEmptyAndTrim(limit); 85 | } 86 | 87 | @DataBoundSetter 88 | public void setTags(String tags) { 89 | this.tags = Util.fixEmptyAndTrim(tags); 90 | } 91 | 92 | @DataBoundSetter 93 | public void setSkippedTags(String skippedTags) { 94 | this.skippedTags = Util.fixEmptyAndTrim(skippedTags); 95 | } 96 | 97 | @DataBoundSetter 98 | public void setStartAtTask(String startAtTask) { 99 | this.startAtTask = Util.fixEmptyAndTrim(startAtTask); 100 | } 101 | 102 | @DataBoundSetter 103 | public void setExtras(String extras) { 104 | this.extras = Util.fixEmptyAndTrim(extras); 105 | } 106 | 107 | public String getInstallation() { 108 | return installation; 109 | } 110 | 111 | public String getPlaybook() { 112 | return playbook; 113 | } 114 | 115 | public String getInventory() { 116 | return inventory; 117 | } 118 | 119 | public String getCredentialsId() { 120 | return credentialsId; 121 | } 122 | 123 | public boolean isSudo() { 124 | return sudo; 125 | } 126 | 127 | public String getSudoUser() { 128 | return sudoUser; 129 | } 130 | 131 | public String getLimit() { 132 | return limit; 133 | } 134 | 135 | public String getTags() { 136 | return tags; 137 | } 138 | 139 | public String getSkippedTags() { 140 | return skippedTags; 141 | } 142 | 143 | public String getStartAtTask() { 144 | return startAtTask; 145 | } 146 | 147 | public String getExtras() { 148 | return extras; 149 | } 150 | 151 | @Extension 152 | public static final class DescriptorImpl extends AbstractStepDescriptorImpl { 153 | 154 | public DescriptorImpl() { 155 | super(AnsiblePlaybookExecution.class); 156 | } 157 | 158 | @Override 159 | public String getFunctionName() { 160 | return "ansiblePlaybook"; 161 | } 162 | 163 | @Override 164 | public String getDisplayName() { 165 | return "Invoke an ansible playbook"; 166 | } 167 | 168 | public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Project project) { 169 | return new StandardListBoxModel() 170 | .withEmptySelection() 171 | .withMatching(anyOf( 172 | instanceOf(SSHUserPrivateKey.class), 173 | instanceOf(UsernamePasswordCredentials.class)), 174 | CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, project)); 175 | } 176 | 177 | public ListBoxModel doFillInstallationItems() { 178 | ListBoxModel model = new ListBoxModel(); 179 | for (AnsibleInstallation tool : AnsibleInstallation.allInstallations()) { 180 | model.add(tool.getName()); 181 | } 182 | return model; 183 | } 184 | } 185 | 186 | public static final class AnsiblePlaybookExecution extends AbstractSynchronousStepExecution { 187 | 188 | private static final long serialVersionUID = 1; 189 | 190 | @Inject 191 | private transient AnsiblePlaybookStep step; 192 | 193 | @StepContextParameter 194 | private transient TaskListener listener; 195 | 196 | @StepContextParameter 197 | private transient Launcher launcher; 198 | 199 | @StepContextParameter 200 | private transient Run run; 201 | 202 | @StepContextParameter 203 | private transient FilePath ws; 204 | 205 | @StepContextParameter 206 | private transient EnvVars envVars; 207 | 208 | @StepContextParameter 209 | private transient Computer computer; 210 | 211 | @Override 212 | protected Void run() throws Exception { 213 | Inventory inventory = StringUtils.isNotBlank(step.getInventory()) ? new InventoryPath(step.getInventory()) : null; 214 | AnsiblePlaybookBuilder builder = new AnsiblePlaybookBuilder(step.getPlaybook(), inventory); 215 | builder.setAnsibleName(step.getInstallation()); 216 | builder.setSudo(step.isSudo()); 217 | builder.setSudoUser(step.getSudoUser()); 218 | builder.setCredentialsId(step.getCredentialsId(), true); 219 | builder.setForks(5); 220 | builder.setLimit(step.getLimit()); 221 | builder.setTags(step.getTags()); 222 | builder.setStartAtTask(step.getStartAtTask()); 223 | builder.setSkippedTags(step.getSkippedTags()); 224 | builder.setAdditionalParameters(step.getExtras()); 225 | builder.setHostKeyChecking(false); 226 | builder.setUnbufferedOutput(true); 227 | builder.setColorizedOutput(false); 228 | builder.perform(run, computer.getNode(), ws, launcher, listener, envVars); 229 | return null; 230 | } 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 |
2 | Invoke Ansible Ad-Hoc commands and playbooks. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/config.properties: -------------------------------------------------------------------------------- 1 | AnsibleInstallation.error=Needs to know where Ansible is installed.
\ 2 | Please do so from the system configuration. 3 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-additionalParameters.html: -------------------------------------------------------------------------------- 1 |
2 | Any additional parameters to pass to the ansible command. 3 |

Warning:

The content of this textbox will be passed as is to the command line. 4 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-colorizedOutput.html: -------------------------------------------------------------------------------- 1 |
2 | Check this box to allow ansible to render ANSI color codes in the Jenkins console. 3 | This option works well with the Jenkins AnsiColor plugin. 4 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-command.html: -------------------------------------------------------------------------------- 1 |
2 | Module arguments or shell command to execute 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-credentialsId.html: -------------------------------------------------------------------------------- 1 |
2 | Select the credentials for the SSH connections. Only private key authentication is supported. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-forks.html: -------------------------------------------------------------------------------- 1 |
2 | Specify number of parallel processes to use 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-hostKeyChecking.html: -------------------------------------------------------------------------------- 1 |
2 | Check this box to enforce the validation of the hosts SSH server keys. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-hostPattern.html: -------------------------------------------------------------------------------- 1 |
2 | The host or set of hosts on which the command will be executed. For more details see the ansible documentation 3 | for Patterns. 4 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-module.html: -------------------------------------------------------------------------------- 1 |
2 | Module name to execute. The shell module is used when left empty. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-sudo.html: -------------------------------------------------------------------------------- 1 |
2 | Run operations with sudo. It works only when the remote user is sudoer with nopasswd option. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-sudoUser.html: -------------------------------------------------------------------------------- 1 |
2 | Desired sudo user. "root" is used when this field is empty. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-unbufferedOutput.html: -------------------------------------------------------------------------------- 1 |
2 | Skip standard output buffering for the ansible process. The ansible output is directly rendered into the Jenkins 3 | console. This option can be usefull for long running operations. 4 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleInstallation/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/config.properties: -------------------------------------------------------------------------------- 1 | AnsibleInstallation.error=Needs to know where Ansible is installed.
\ 2 | Please do so from the system configuration. 3 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-additionalParameters.html: -------------------------------------------------------------------------------- 1 |
2 | Any additional parameters to pass to the ansible command. 3 |

Warning:

The content of this textbox will be passed as is to the command line. 4 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-colorizedOutput.html: -------------------------------------------------------------------------------- 1 |
2 | Check this box to allow ansible to render ANSI color codes in the Jenkins console. 3 | This option works well with the Jenkins AnsiColor plugin. 4 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-credentialsId.html: -------------------------------------------------------------------------------- 1 |
2 | Select the credentials for the SSH connections. Only private key authentication is supported. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-forks.html: -------------------------------------------------------------------------------- 1 |
2 | Specify number of parallel processes to use 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-hostKeyChecking.html: -------------------------------------------------------------------------------- 1 |
2 | Check this box to enforce the validation of the hosts SSH server keys. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-limit.html: -------------------------------------------------------------------------------- 1 |
2 | Further limit selected hosts to an additional pattern. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-playbook.html: -------------------------------------------------------------------------------- 1 |
2 | Path to the ansible playbook file. The path can be absolute or relative to the job workspace. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-skippedTags.html: -------------------------------------------------------------------------------- 1 |
2 | only run plays and tasks whose tags do not match these values. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-startAtTask.html: -------------------------------------------------------------------------------- 1 |
2 | Start the playbook at the task matching this name. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-sudo.html: -------------------------------------------------------------------------------- 1 |
2 | Run operations with sudo. It works only when the remote user is sudoer with nopasswd option. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-sudoUser.html: -------------------------------------------------------------------------------- 1 |
2 | Desired sudo user. "root" is used when this field is empty. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-tags.html: -------------------------------------------------------------------------------- 1 |
2 | Only run plays and tasks tagged with these values. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-unbufferedOutput.html: -------------------------------------------------------------------------------- 1 |
2 | Skip standard output buffering for the ansible process. The ansible output is directly rendered into the Jenkins 3 | console. This option can be usefull for long running operations. 4 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/InventoryContent/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/InventoryContent/help-dynamic.html: -------------------------------------------------------------------------------- 1 |
2 | Check this box if a dynamic inventory is used. For more details see the ansible documentation for Dynamic Inventory 4 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/InventoryPath/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/workflow/AnsiblePlaybookStep/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/workflow/AnsiblePlaybookStep/help.html: -------------------------------------------------------------------------------- 1 |
2 | Execute an Ansible playbook. Only the playbook parameter is mandatory. 3 |
4 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandInvocationTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.mockito.Matchers.any; 5 | import static org.mockito.Matchers.anyMap; 6 | import static org.mockito.Mockito.mock; 7 | import static org.mockito.Mockito.verify; 8 | import static org.mockito.Mockito.when; 9 | 10 | import java.io.File; 11 | import java.util.Map; 12 | 13 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 14 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; 15 | import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; 16 | import hudson.EnvVars; 17 | import hudson.FilePath; 18 | import hudson.Launcher; 19 | import hudson.model.AbstractBuild; 20 | import hudson.model.BuildListener; 21 | import hudson.model.TaskListener; 22 | import hudson.util.ArgumentListBuilder; 23 | import hudson.util.Secret; 24 | import org.junit.*; 25 | import org.mockito.ArgumentCaptor; 26 | 27 | /** 28 | * Created with IntelliJ IDEA. 29 | * User: jcsirot 30 | * Date: 22/05/15 31 | * Time: 19:30 32 | * To change this template use File | Settings | File Templates. 33 | */ 34 | public class AnsibleAdHocCommandInvocationTest { 35 | 36 | @Test 37 | public void should_generate_simple_invocation() throws Exception { 38 | // Given 39 | Inventory inventory = new InventoryPath("/tmp/hosts"); 40 | BuildListener listener = mock(BuildListener.class); 41 | CLIRunner runner = mock(CLIRunner.class); 42 | AbstractBuild build = mock(AbstractBuild.class); 43 | when(build.getEnvironment(any(TaskListener.class))).thenReturn(new EnvVars()); 44 | AnsibleAdHocCommandInvocation invocation = new AnsibleAdHocCommandInvocation("/usr/local/bin/ansible", build, listener); 45 | invocation.setHostPattern("localhost"); 46 | invocation.setInventory(inventory); 47 | invocation.setModule("ping"); 48 | invocation.setForks(5); 49 | // When 50 | invocation.execute(runner); 51 | // Then 52 | ArgumentCaptor argument = ArgumentCaptor.forClass(ArgumentListBuilder.class); 53 | verify(runner).execute(argument.capture(), anyMap()); 54 | assertThat(argument.getValue().toString()) 55 | .isEqualTo("/usr/local/bin/ansible localhost -i /tmp/hosts -m ping -f 5"); 56 | } 57 | 58 | @Test 59 | public void should_generate_simple_invocation_with_env() throws Exception { 60 | // Given 61 | Inventory inventory = new InventoryPath("/tmp/hosts"); 62 | BuildListener listener = mock(BuildListener.class); 63 | CLIRunner runner = mock(CLIRunner.class); 64 | AbstractBuild build = mock(AbstractBuild.class); 65 | when(build.getEnvironment(any(TaskListener.class))).thenReturn(new EnvVars()); 66 | AnsibleAdHocCommandInvocation invocation = new AnsibleAdHocCommandInvocation("/usr/local/bin/ansible", build, listener); 67 | invocation.setHostPattern("localhost"); 68 | invocation.setInventory(inventory); 69 | invocation.setModule("ping"); 70 | invocation.setForks(5); 71 | invocation.setColorizedOutput(true); 72 | invocation.setHostKeyCheck(false); 73 | invocation.setUnbufferedOutput(true); 74 | // When 75 | invocation.execute(runner); 76 | // Then 77 | ArgumentCaptor argument = ArgumentCaptor.forClass(Map.class); 78 | verify(runner).execute(any(ArgumentListBuilder.class), argument.capture()); 79 | assertThat((Map)argument.getValue()) 80 | .containsEntry("PYTHONUNBUFFERED", "1") 81 | .containsEntry("ANSIBLE_FORCE_COLOR", "true") 82 | .containsEntry("ANSIBLE_HOST_KEY_CHECKING", "False"); 83 | } 84 | 85 | @Test 86 | @Ignore("build.getWorkspace() cannot be mocked") 87 | public void should_handle_private_key_credentials() throws Exception { 88 | // Given 89 | Inventory inventory = new InventoryPath("/tmp/hosts"); 90 | SSHUserPrivateKey pkey = mock(SSHUserPrivateKey.class); 91 | when(pkey.getUsername()).thenReturn("mylogin"); 92 | BuildListener listener = mock(BuildListener.class); 93 | CLIRunner runner = mock(CLIRunner.class); 94 | AbstractBuild build = mock(AbstractBuild.class); 95 | when(build.getEnvironment(any(TaskListener.class))).thenReturn(new EnvVars()); 96 | AnsibleAdHocCommandInvocation invocation = new AnsibleAdHocCommandInvocation("/usr/local/bin/ansible", build, listener); 97 | invocation.setHostPattern("localhost"); 98 | invocation.setInventory(inventory); 99 | invocation.setModule("ping"); 100 | invocation.setCredentials(pkey); 101 | invocation.setForks(5); 102 | // When 103 | invocation.execute(runner); 104 | // Then 105 | ArgumentCaptor argument = ArgumentCaptor.forClass(ArgumentListBuilder.class); 106 | verify(runner).execute(argument.capture(), anyMap()); 107 | assertThat(argument.getValue().toString()) 108 | .matches("/usr/local/bin/ansible localhost -i /tmp/hosts -m ping -f 5 --private-key .+ -u mylogin"); 109 | } 110 | 111 | @Test 112 | @Ignore("Secret can neither be instanced nor mocked") 113 | public void should_handle_password_credentials() throws Exception { 114 | // Given 115 | Inventory inventory = new InventoryPath("/tmp/hosts"); 116 | StandardUsernamePasswordCredentials password = mock(StandardUsernamePasswordCredentials.class); 117 | when(password.getUsername()).thenReturn("mylogin"); 118 | when(password.getPassword()).thenReturn(Secret.fromString("aStrongSecretPassword")); 119 | BuildListener listener = mock(BuildListener.class); 120 | CLIRunner runner = mock(CLIRunner.class); 121 | AbstractBuild build = mock(AbstractBuild.class); 122 | when(build.getEnvironment(any(TaskListener.class))).thenReturn(new EnvVars()); 123 | AnsibleAdHocCommandInvocation invocation = new AnsibleAdHocCommandInvocation("/usr/local/bin/ansible", build, listener); 124 | invocation.setHostPattern("localhost"); 125 | invocation.setInventory(inventory); 126 | invocation.setModule("ping"); 127 | invocation.setCredentials(password); 128 | invocation.setForks(5); 129 | // When 130 | invocation.execute(runner); 131 | // Then 132 | ArgumentCaptor argument = ArgumentCaptor.forClass(ArgumentListBuilder.class); 133 | verify(runner).execute(argument.capture(), anyMap()); 134 | assertThat(argument.getValue().toString()) 135 | .isEqualTo("sshpass ****** /usr/local/bin/ansible localhost -i /tmp/hosts -m ping -f 5 " + 136 | "-u" + 137 | " mylogin -k"); 138 | } 139 | 140 | @Test 141 | public void should_handle_variables() throws Exception { 142 | // Given 143 | Inventory inventory = new InventoryPath("/tmp/hosts"); 144 | BuildListener listener = mock(BuildListener.class); 145 | CLIRunner runner = mock(CLIRunner.class); 146 | AbstractBuild build = mock(AbstractBuild.class); 147 | EnvVars vars = new EnvVars(); 148 | vars.put("MODULE", "ping"); 149 | when(build.getEnvironment(any(TaskListener.class))).thenReturn(vars); 150 | AnsibleAdHocCommandInvocation invocation = new AnsibleAdHocCommandInvocation("/usr/local/bin/ansible", build, listener); 151 | invocation.setHostPattern("localhost"); 152 | invocation.setInventory(inventory); 153 | invocation.setModule("${MODULE}"); 154 | invocation.setForks(5); 155 | // When 156 | invocation.execute(runner); 157 | // Then 158 | ArgumentCaptor argument = ArgumentCaptor.forClass(ArgumentListBuilder.class); 159 | verify(runner).execute(argument.capture(), anyMap()); 160 | assertThat(argument.getValue().toString()) 161 | .isEqualTo("/usr/local/bin/ansible localhost -i /tmp/hosts -m ping -f 5"); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ansible/jobdsl/DslJobRule.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible.jobdsl; 2 | 3 | import com.google.common.base.Charsets; 4 | import com.google.common.io.Resources; 5 | import hudson.model.FreeStyleProject; 6 | import javaposse.jobdsl.plugin.ExecuteDslScripts; 7 | import javaposse.jobdsl.plugin.LookupStrategy; 8 | import javaposse.jobdsl.plugin.RemovedJobAction; 9 | import javaposse.jobdsl.plugin.RemovedViewAction; 10 | import org.junit.rules.TestRule; 11 | import org.junit.runner.Description; 12 | import org.junit.runners.model.Statement; 13 | import org.jvnet.hudson.test.JenkinsRule; 14 | 15 | import java.lang.annotation.Retention; 16 | import java.lang.annotation.Target; 17 | 18 | import static java.lang.annotation.ElementType.METHOD; 19 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 20 | import static org.hamcrest.Matchers.hasItem; 21 | import static org.hamcrest.Matchers.is; 22 | import static org.junit.Assert.assertThat; 23 | 24 | /** 25 | * @author lanwen (Merkushev Kirill) 26 | */ 27 | public class DslJobRule implements TestRule { 28 | 29 | public static final String JOB_NAME_IN_DSL_SCRIPT = "ansible"; 30 | 31 | private JenkinsRule jRule; 32 | 33 | private FreeStyleProject generated; 34 | 35 | public DslJobRule(JenkinsRule jRule) { 36 | this.jRule = jRule; 37 | } 38 | 39 | @Override 40 | public Statement apply(final Statement base, final Description description) { 41 | return new Statement() { 42 | @Override 43 | public void evaluate() throws Throwable { 44 | before(description); 45 | base.evaluate(); 46 | } 47 | }; 48 | } 49 | 50 | private void before(Description description) throws Exception { 51 | FreeStyleProject job = jRule.createFreeStyleProject(); 52 | String script = description.getAnnotation(WithJobDsl.class).value(); 53 | String scriptText = Resources.toString(Resources.getResource(script), Charsets.UTF_8); 54 | 55 | job.getBuildersList().add( 56 | new ExecuteDslScripts( 57 | new ExecuteDslScripts.ScriptLocation( 58 | null, null, 59 | scriptText 60 | ), 61 | false, 62 | RemovedJobAction.DELETE, 63 | RemovedViewAction.DELETE, 64 | LookupStrategy.JENKINS_ROOT 65 | ) 66 | ); 67 | 68 | jRule.buildAndAssertSuccess(job); 69 | 70 | assertThat(jRule.getInstance().getJobNames(), hasItem(is(JOB_NAME_IN_DSL_SCRIPT))); 71 | 72 | generated = jRule.getInstance().getItemByFullName(JOB_NAME_IN_DSL_SCRIPT, FreeStyleProject.class); 73 | } 74 | 75 | public FreeStyleProject getGeneratedJob() { 76 | return generated; 77 | } 78 | 79 | @Target(METHOD) 80 | @Retention(RUNTIME) 81 | public @interface WithJobDsl { 82 | String value(); 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ansible/jobdsl/JobDslIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible.jobdsl; 2 | 3 | import org.hamcrest.Matcher; 4 | import org.jenkinsci.plugins.ansible.AnsibleAdHocCommandBuilder; 5 | import org.jenkinsci.plugins.ansible.AnsiblePlaybookBuilder; 6 | import org.jenkinsci.plugins.ansible.InventoryContent; 7 | import org.jenkinsci.plugins.ansible.InventoryPath; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.junit.rules.RuleChain; 11 | import org.jvnet.hudson.test.JenkinsRule; 12 | 13 | import static org.hamcrest.Matchers.is; 14 | import static org.hamcrest.Matchers.isA; 15 | import static org.hamcrest.Matchers.notNullValue; 16 | import static org.junit.Assert.assertThat; 17 | 18 | /** 19 | * @author lanwen (Merkushev Kirill) 20 | */ 21 | public class JobDslIntegrationTest { 22 | public static final String ANSIBLE_DSL_GROOVY_PLAYBOOK = "jobdsl/playbook.groovy"; 23 | public static final String ANSIBLE_DSL_GROOVY_ADHOC = "jobdsl/adhoc.groovy"; 24 | 25 | public JenkinsRule jenkins = new JenkinsRule(); 26 | public DslJobRule dsl = new DslJobRule(jenkins); 27 | 28 | @Rule 29 | public RuleChain chain = RuleChain.outerRule(jenkins).around(dsl); 30 | 31 | @Test 32 | @DslJobRule.WithJobDsl(ANSIBLE_DSL_GROOVY_PLAYBOOK) 33 | public void shouldCreateJobWithPlaybookDsl() throws Exception { 34 | AnsiblePlaybookBuilder step = dsl.getGeneratedJob().getBuildersList().get(AnsiblePlaybookBuilder.class); 35 | assertThat("Should add playbook builder", step, notNullValue()); 36 | 37 | assertThat("playbook", step.playbook, is("path/playbook.yml")); 38 | assertThat("inventory", step.inventory, (Matcher) isA(InventoryPath.class)); 39 | assertThat("ansibleName", step.ansibleName, is("1.9.4")); 40 | assertThat("limit", step.limit, is("retry.limit")); 41 | assertThat("tags", step.tags, is("one,two")); 42 | assertThat("skippedTags", step.skippedTags, is("three")); 43 | assertThat("startAtTask", step.startAtTask, is("task")); 44 | assertThat("credentialsId", step.credentialsId, is("credsid")); 45 | assertThat("sudo", step.sudo, is(true)); 46 | assertThat("sudoUser", step.sudoUser, is("user")); 47 | assertThat("forks", step.forks, is(6)); 48 | assertThat("unbufferedOutput", step.unbufferedOutput, is(false)); 49 | assertThat("colorizedOutput", step.colorizedOutput, is(true)); 50 | assertThat("hostKeyChecking", step.hostKeyChecking, is(false)); 51 | assertThat("additionalParameters", step.additionalParameters, is("params")); 52 | } 53 | 54 | @Test 55 | @DslJobRule.WithJobDsl(ANSIBLE_DSL_GROOVY_ADHOC) 56 | public void shouldCreateJobAdhocDsl() throws Exception { 57 | AnsibleAdHocCommandBuilder step = dsl.getGeneratedJob().getBuildersList().get(AnsibleAdHocCommandBuilder.class); 58 | assertThat("Should add adhoc builder", step, notNullValue()); 59 | 60 | assertThat("module", step.module, is("module")); 61 | assertThat("inventory", step.inventory, (Matcher) isA(InventoryContent.class)); 62 | assertThat("ansibleName", step.ansibleName, is("1.9.1")); 63 | 64 | assertThat("credentialsId", step.credentialsId, is("credsid")); 65 | assertThat("hostPattern", step.hostPattern, is("pattern")); 66 | assertThat("sudo", step.sudo, is(false)); 67 | assertThat("sudoUser", step.sudoUser, is("root")); 68 | assertThat("forks", step.forks, is(5)); 69 | assertThat("unbufferedOutput", step.unbufferedOutput, is(true)); 70 | assertThat("colorizedOutput", step.colorizedOutput, is(false)); 71 | assertThat("hostKeyChecking", step.hostKeyChecking, is(false)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/resources/jobdsl/adhoc.groovy: -------------------------------------------------------------------------------- 1 | freeStyleJob('ansible') { 2 | steps { 3 | ansibleAdHoc('module', 'command') { 4 | ansibleName('1.9.1') 5 | credentialsId('credsid') 6 | hostPattern('pattern') 7 | inventoryContent('content') 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/test/resources/jobdsl/playbook.groovy: -------------------------------------------------------------------------------- 1 | freeStyleJob('ansible') { 2 | steps { 3 | ansiblePlaybook('path/playbook.yml') { 4 | inventoryPath('hosts.ini') 5 | ansibleName('1.9.4') 6 | limit('retry.limit') 7 | tags('one,two') 8 | skippedTags('three') 9 | startAtTask('task') 10 | credentialsId('credsid') 11 | sudo(true) 12 | sudoUser("user") 13 | forks(6) 14 | unbufferedOutput(false) 15 | colorizedOutput(true) 16 | hostKeyChecking(false) 17 | additionalParameters('params') 18 | } 19 | } 20 | } --------------------------------------------------------------------------------