├── .gitattributes ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── cd.yaml │ └── jenkins-security-scan.yml ├── .mvn ├── maven.config └── extensions.xml ├── .git-blame-ignore-revs ├── src ├── main │ ├── resources │ │ ├── org │ │ │ └── jenkinsci │ │ │ │ └── plugins │ │ │ │ └── ansible │ │ │ │ ├── AnsibleVaultBuilder │ │ │ │ ├── help-action.html │ │ │ │ ├── help-content.html │ │ │ │ ├── help-input.html │ │ │ │ ├── help-output.html │ │ │ │ ├── help-vaultCredentialsId.html │ │ │ │ ├── help-vaultTmpPath.html │ │ │ │ ├── help-newVaultCredentialsId.html │ │ │ │ ├── config.properties │ │ │ │ └── config.jelly │ │ │ │ ├── AnsibleAdHocCommandBuilder │ │ │ │ ├── help-command.html │ │ │ │ ├── help-module.html │ │ │ │ ├── help-becomeUser.html │ │ │ │ ├── help-forks.html │ │ │ │ ├── help-credentialsId.html │ │ │ │ ├── help-become.html │ │ │ │ ├── help-disableHostKeyChecking.html │ │ │ │ ├── help-vaultCredentialsId.html │ │ │ │ ├── help-vaultTmpPath.html │ │ │ │ ├── config.properties │ │ │ │ ├── help-sudoUser.html │ │ │ │ ├── help-additionalParameters.html │ │ │ │ ├── help-sudo.html │ │ │ │ ├── help-unbufferedOutput.html │ │ │ │ ├── help-hostPattern.html │ │ │ │ ├── help-colorizedOutput.html │ │ │ │ └── config.jelly │ │ │ │ ├── AnsiblePlaybookBuilder │ │ │ │ ├── help-tags.html │ │ │ │ ├── help-limit.html │ │ │ │ ├── help-startAtTask.html │ │ │ │ ├── help-becomeUser.html │ │ │ │ ├── help-skippedTags.html │ │ │ │ ├── help-forks.html │ │ │ │ ├── help-playbook.html │ │ │ │ ├── help-credentialsId.html │ │ │ │ ├── help-become.html │ │ │ │ ├── help-checkMode.html │ │ │ │ ├── help-disableHostKeyChecking.html │ │ │ │ ├── help-vaultCredentialsId.html │ │ │ │ ├── help-vaultTmpPath.html │ │ │ │ ├── config.properties │ │ │ │ ├── help-sudoUser.html │ │ │ │ ├── help-additionalParameters.html │ │ │ │ ├── help-sudo.html │ │ │ │ ├── help-unbufferedOutput.html │ │ │ │ ├── help-colorizedOutput.html │ │ │ │ └── config.jelly │ │ │ │ ├── InventoryPath │ │ │ │ ├── help-path.html │ │ │ │ └── config.jelly │ │ │ │ ├── InventoryDoNotSpecify │ │ │ │ └── config.jelly │ │ │ │ ├── workflow │ │ │ │ ├── AnsiblePlaybookStep │ │ │ │ │ ├── help.html │ │ │ │ │ └── config.jelly │ │ │ │ ├── AnsibleVaultStep │ │ │ │ │ ├── help.html │ │ │ │ │ └── config.jelly │ │ │ │ └── AnsibleAdHocStep │ │ │ │ │ ├── help.html │ │ │ │ │ └── config.jelly │ │ │ │ ├── InventoryContent │ │ │ │ ├── help-dynamic.html │ │ │ │ └── config.jelly │ │ │ │ └── AnsibleInstallation │ │ │ │ └── config.jelly │ │ └── index.jelly │ └── java │ │ └── org │ │ └── jenkinsci │ │ └── plugins │ │ └── ansible │ │ ├── AnsibleInvocationException.java │ │ ├── jobdsl │ │ ├── context │ │ │ ├── ExtraVarsContext.java │ │ │ └── AnsibleContext.java │ │ └── AnsibleJobDslExtension.java │ │ ├── AnsibleCommand.java │ │ ├── InventoryDoNotSpecify.java │ │ ├── CLIRunner.java │ │ ├── InventoryPath.java │ │ ├── Inventory.java │ │ ├── ExtraVar.java │ │ ├── InventoryContent.java │ │ ├── AnsibleAdHocCommandInvocation.java │ │ ├── Utils.java │ │ ├── AnsiblePlaybookInvocation.java │ │ ├── AbstractAnsibleBuilderDescriptor.java │ │ ├── AnsibleInstallation.java │ │ ├── AnsibleVaultInvocation.java │ │ ├── AnsibleVaultBuilder.java │ │ ├── workflow │ │ ├── AnsibleVaultStep.java │ │ └── AnsibleAdhocStep.java │ │ ├── AnsibleAdHocCommandBuilder.java │ │ ├── AbstractAnsibleInvocation.java │ │ └── AnsiblePlaybookBuilder.java └── test │ ├── resources │ ├── jobdsl │ │ ├── vault.groovy │ │ ├── adhoc.groovy │ │ ├── checkMode.groovy │ │ ├── playbookBuilder.groovy │ │ ├── expander.groovy │ │ ├── security630.groovy │ │ ├── playbook.groovy │ │ └── legacyPlaybook.groovy │ ├── pipelines │ │ ├── minimal.groovy │ │ ├── minimalCheckMode.groovy │ │ ├── extraVarsHiddenString.groovy │ │ ├── extraVarsNumeric.groovy │ │ ├── extraVarsBoolean.groovy │ │ ├── vaultCredentialsFile.groovy │ │ ├── ansiblePlaybookSshPass.groovy │ │ ├── vaultCredentialsString.groovy │ │ ├── adhocCommand.groovy │ │ ├── vaultTmpPath.groovy │ │ ├── vaultCredentialsFileViaExtras.groovy │ │ └── extraVarsMap.groovy │ ├── docker │ │ └── Dockerfile │ └── org │ │ └── jenkinsci │ │ └── plugins │ │ └── ansible │ │ └── CompatibilityTest │ │ └── jobs │ │ └── old │ │ └── config.xml │ └── java │ └── org │ └── jenkinsci │ └── plugins │ └── ansible │ ├── CompatibilityTest.java │ ├── AnsibleVaultInvocationTest.java │ ├── AnsibleAdHocCommandInvocationTest.java │ └── jobdsl │ └── JobDslIntegrationTest.java ├── .gitignore ├── docs └── images │ └── jenkins-deploy-ansible-console.png ├── Jenkinsfile ├── LICENSE.md └── pom.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.png binary 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/ansible-plugin-developers 2 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -Dchangelist.format=%d.v%s -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # .git-blame-ignore-revs 2 | # Spotless formatting 3 | 6eb11c018eb7640cf1dbf1fd5642355e0286058e 4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleVaultBuilder/help-action.html: -------------------------------------------------------------------------------- 1 |
2 | Desired vault action. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleVaultBuilder/help-content.html: -------------------------------------------------------------------------------- 1 |
2 | Desired encrypted content. 3 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | *~ 3 | .DS_Store 4 | /work/ 5 | /.idea/ 6 | *.iml 7 | .classpath 8 | .project 9 | .settings/ 10 | .java-version -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleVaultBuilder/help-input.html: -------------------------------------------------------------------------------- 1 |
2 | Desired input file to encrypt. 3 |
-------------------------------------------------------------------------------- /docs/images/jenkins-deploy-ansible-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/ansible-plugin/main/docs/images/jenkins-deploy-ansible-console.png -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleVaultBuilder/help-output.html: -------------------------------------------------------------------------------- 1 |
2 | Desired output file for encrypted content. 3 |
-------------------------------------------------------------------------------- /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/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-limit.html: -------------------------------------------------------------------------------- 1 |
2 | Further limit selected hosts to an additional pattern. 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/InventoryPath/help-path.html: -------------------------------------------------------------------------------- 1 |
2 | Specify the inventory host path or a comma separated host list 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | Invoke Ansible Ad-Hoc commands and playbooks. 4 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/InventoryDoNotSpecify/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /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/AnsiblePlaybookBuilder/help-becomeUser.html: -------------------------------------------------------------------------------- 1 |
2 | Desired become user. "root" is used when this field is empty. 3 |
4 | -------------------------------------------------------------------------------- /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/AnsibleAdHocCommandBuilder/help-becomeUser.html: -------------------------------------------------------------------------------- 1 |
2 | Desired become user. "root" is used when this field is empty. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-forks.html: -------------------------------------------------------------------------------- 1 |
2 | Specify number of parallel processes to use. Set to 0 to use the default value. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-forks.html: -------------------------------------------------------------------------------- 1 |
2 | Specify number of parallel processes to use. Set to 0 to use the default value. 3 |
-------------------------------------------------------------------------------- /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/main/resources/org/jenkinsci/plugins/ansible/workflow/AnsibleVaultStep/help.html: -------------------------------------------------------------------------------- 1 |
2 | Execute Ansible vault. Only the vaultCredentialsId parameter is mandatory. 3 |
4 | -------------------------------------------------------------------------------- /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-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-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-become.html: -------------------------------------------------------------------------------- 1 |
2 | Run operations with become. It works only when the remote user is sudoer with nopasswd option. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-checkMode.html: -------------------------------------------------------------------------------- 1 |
2 | Run ansible with check mode enabled. This will not make any changes on the remote system (--check) 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-become.html: -------------------------------------------------------------------------------- 1 |
2 | Run operations with become. It works only when the remote user is sudoer with nopasswd option. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-disableHostKeyChecking.html: -------------------------------------------------------------------------------- 1 |
2 | Check this box if you really want to disable the validation of the hosts SSH server keys. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-vaultCredentialsId.html: -------------------------------------------------------------------------------- 1 |
2 | Select the credentials for vault encrypted vars. Only secret file and secret text are supported. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-disableHostKeyChecking.html: -------------------------------------------------------------------------------- 1 |
2 | Check this box if you really want to disable the validation of the hosts SSH server keys. 3 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-vaultCredentialsId.html: -------------------------------------------------------------------------------- 1 |
2 | Select the credentials for vault encrypted vars. Only secret file and secret text are supported. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleVaultBuilder/help-vaultCredentialsId.html: -------------------------------------------------------------------------------- 1 |
2 | Select the credentials for vault encrypted vars. Only Jenkins secret file and secret text are supported. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/workflow/AnsibleAdHocStep/help.html: -------------------------------------------------------------------------------- 1 |
2 | Execute an Ansible Adhoc command. Only hosts and moduleArguments parameters are mandatories. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder/help-vaultTmpPath.html: -------------------------------------------------------------------------------- 1 |
2 | Insert the path where to store temporary generated vault password files, ssh keys, etc... Default is in workspace. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleVaultBuilder/help-vaultTmpPath.html: -------------------------------------------------------------------------------- 1 |
2 | Insert the path where to store temporary generated vault password files, ssh keys, etc... Default is in workspace. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder/help-vaultTmpPath.html: -------------------------------------------------------------------------------- 1 |
2 | Insert the path where to store temporary generated vault password files, ssh keys, etc... Default is in workspace. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleVaultBuilder/help-newVaultCredentialsId.html: -------------------------------------------------------------------------------- 1 |
2 | Select the credentials for rekeying vault encrypted files. Only Jenkins secret file and secret text are supported. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleVaultBuilder/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/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-sudoUser.html: -------------------------------------------------------------------------------- 1 |
2 | Desired sudo user. "root" is used when this field is empty. Sudo has been deprecated in favor of become and will be removed in Ansible 2.6. 3 |
4 | -------------------------------------------------------------------------------- /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-sudoUser.html: -------------------------------------------------------------------------------- 1 |
2 | Desired sudo user. "root" is used when this field is empty. Sudo has been deprecated in favor of become and will be removed in Ansible 2.6. 3 |
4 | -------------------------------------------------------------------------------- /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/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/AnsiblePlaybookBuilder/help-sudo.html: -------------------------------------------------------------------------------- 1 |
2 | Run operations with sudo. It works only when the remote user is sudoer with nopasswd option. Sudo has been deprecated in favor of become and will be removed in Ansible 2.6. 3 |
4 | -------------------------------------------------------------------------------- /src/test/resources/jobdsl/vault.groovy: -------------------------------------------------------------------------------- 1 | freeStyleJob('ansible') { 2 | steps { 3 | ansibleVault() { 4 | action('encrypt_string') 5 | content('my_secret') 6 | vaultCredentialsId('ansible_vault_credentials') 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /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. Sudo has been deprecated in favor of become and will be removed in Ansible 2.6. 3 |
4 | -------------------------------------------------------------------------------- /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/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/InventoryPath/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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/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/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/checkMode.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 | checkMode(true) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/pipelines/minimal.groovy: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label('test-agent') 4 | } 5 | stages { 6 | stage('Ansible playbook') { 7 | steps { 8 | ansiblePlaybook(playbook: '/ansible/playbook.yml') 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /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/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/test/resources/pipelines/minimalCheckMode.groovy: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label('test-agent') 4 | } 5 | stages { 6 | stage('Ansible playbook') { 7 | steps { 8 | ansiblePlaybook(playbook: '/ansible/playbook.yml', checkMode: true) 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleInstallation/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "maven" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/InventoryContent/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/test/resources/pipelines/extraVarsHiddenString.groovy: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label('test-agent') 4 | } 5 | stages { 6 | stage('Ansible playbook') { 7 | steps { 8 | ansiblePlaybook( 9 | playbook: '/ansible/playbook.yml', 10 | extraVars: [foo: 'bar'], 11 | ) 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/pipelines/extraVarsNumeric.groovy: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label('test-agent') 4 | } 5 | stages { 6 | stage('Ansible playbook') { 7 | steps { 8 | ansiblePlaybook( 9 | playbook: '/ansible/playbook.yml', 10 | extraVars: [foo1: 8], 11 | ) 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/pipelines/extraVarsBoolean.groovy: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label('test-agent') 4 | } 5 | stages { 6 | stage('Ansible playbook') { 7 | steps { 8 | ansiblePlaybook( 9 | playbook: '/ansible/playbook.yml', 10 | extraVars: [foo1: true, foo2: false], 11 | ) 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.13 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/pipelines/vaultCredentialsFile.groovy: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label('test-agent') 4 | } 5 | stages { 6 | stage('Ansible playbook') { 7 | steps { 8 | ansiblePlaybook( 9 | playbook: '/ansible/playbook.yml', 10 | vaultCredentialsId: 'vaultCredentialsFile', 11 | ) 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/pipelines/ansiblePlaybookSshPass.groovy: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label('test-agent') 4 | } 5 | stages { 6 | stage('Ansible playbook') { 7 | steps { 8 | ansiblePlaybook( 9 | playbook: '/ansible/playbook.yml', 10 | credentialsId: 'usernamePasswordCredentialsId', 11 | ) 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/pipelines/vaultCredentialsString.groovy: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label('test-agent') 4 | } 5 | stages { 6 | stage('Ansible playbook') { 7 | steps { 8 | ansiblePlaybook( 9 | playbook: '/ansible/playbook.yml', 10 | vaultCredentialsId: 'vaultCredentialsString', 11 | ) 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /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/test/resources/pipelines/adhocCommand.groovy: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label('test-agent') 4 | } 5 | stages { 6 | stage('Ansible adhoc command') { 7 | steps { 8 | ansibleAdhoc( 9 | inventory: '/ansible/inventory.yml', 10 | hosts: '127.0.0.1', 11 | moduleArguments: 'echo something', 12 | ) 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins 2 | 3 | name: cd 4 | on: 5 | workflow_dispatch: 6 | check_run: 7 | types: 8 | - completed 9 | 10 | jobs: 11 | maven-cd: 12 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1 13 | secrets: 14 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 15 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} 16 | -------------------------------------------------------------------------------- /src/test/resources/pipelines/vaultTmpPath.groovy: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label('test-agent') 4 | } 5 | stages { 6 | stage('Ansible playbook') { 7 | steps { 8 | ansiblePlaybook( 9 | playbook: '/ansible/playbook.yml', 10 | vaultCredentialsId: 'vaultCredentialsFile', 11 | vaultTmpPath: '/ansible/tmp', 12 | ) 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | /* 2 | See the documentation for more options: 3 | https://github.com/jenkins-infra/pipeline-library/ 4 | */ 5 | buildPlugin( 6 | forkCount: '1C', // run this number of tests in parallel for faster feedback. If the number terminates with a 'C', the value will be multiplied by the number of available CPU cores 7 | useContainerAgent: false, // Set to `false` if you need to use Docker for containerized tests 8 | configurations: [ 9 | [platform: 'linux', jdk: 21], 10 | [platform: 'windows', jdk: 17], 11 | [platform: 'linux', jdk: 25], 12 | ]) 13 | -------------------------------------------------------------------------------- /src/test/resources/jobdsl/playbookBuilder.groovy: -------------------------------------------------------------------------------- 1 | freeStyleJob('ansible') { 2 | steps { 3 | ansiblePlaybookBuilder { 4 | playbook('path/playbook.yml') 5 | inventory { 6 | inventoryDoNotSpecify() 7 | } 8 | unbufferedOutput(true) 9 | extraVars { 10 | extraVar { 11 | key('key') 12 | secretValue(hudson.util.Secret.fromString('value')) 13 | hidden(true) 14 | } 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/pipelines/vaultCredentialsFileViaExtras.groovy: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label('test-agent') 4 | } 5 | stages { 6 | stage('Ansible playbook') { 7 | steps { 8 | withCredentials([file(credentialsId: 'vaultCredentialsFileViaExtras', variable: 'VAULT_FILE')]) { 9 | ansiblePlaybook( 10 | playbook: '/ansible/playbook.yml', 11 | extras: '--vault-password-file $VAULT_FILE', 12 | ) 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/resources/pipelines/extraVarsMap.groovy: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label('test-agent') 4 | } 5 | stages { 6 | stage('Ansible playbook') { 7 | steps { 8 | ansiblePlaybook( 9 | playbook: '/ansible/playbook.yml', 10 | extraVars: [foo1: [value: 'bar1', hidden: false]], 11 | ) 12 | ansiblePlaybook( 13 | playbook: '/ansible/playbook.yml', 14 | extraVars: [foo2: [value: 'bar2', hidden: true]], 15 | ) 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/resources/jobdsl/expander.groovy: -------------------------------------------------------------------------------- 1 | job('ansible') { 2 | steps { 3 | shell('''cat > playbook.yml << EOL 4 | - hosts: localhost 5 | connection: local 6 | gather_facts: no 7 | tasks: 8 | - debug: msg=test 9 | EOL 10 | ''') 11 | shell('mkdir -p inventory') 12 | ansiblePlaybook('playbook.yml') { 13 | inventoryPath('${inventory_repository}/inventory.yml') 14 | vaultCredentialsId('${vault_credentials_id}') 15 | credentialsId('${credentials_id}') 16 | } 17 | } 18 | parameters { 19 | stringParam('inventory_repository') 20 | stringParam('vault_credentials_id') 21 | stringParam('credentials_id') 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/resources/jobdsl/security630.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 | become(true) 12 | becomeUser("user") 13 | forks(6) 14 | unbufferedOutput(false) 15 | colorizedOutput(true) 16 | additionalParameters('params') 17 | extraVars { 18 | extraVar ("key","value",true) 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /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 | become(true) 12 | becomeUser("user") 13 | forks(6) 14 | unbufferedOutput(false) 15 | colorizedOutput(true) 16 | disableHostKeyChecking(false) 17 | additionalParameters('params') 18 | extraVars { 19 | extraVar ("key","value",true) 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/resources/jobdsl/legacyPlaybook.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 | disableHostKeyChecking(true) 18 | additionalParameters('params') 19 | extraVars { 20 | extraVar ("key","value",true) 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ansible/CompatibilityTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | 6 | import hudson.model.FreeStyleProject; 7 | import org.junit.jupiter.api.Test; 8 | import org.jvnet.hudson.test.JenkinsRule; 9 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 10 | import org.jvnet.hudson.test.recipes.LocalData; 11 | 12 | @WithJenkins 13 | class CompatibilityTest { 14 | 15 | @LocalData 16 | @Test 17 | void test(JenkinsRule r) { 18 | FreeStyleProject p = (FreeStyleProject) r.jenkins.getItem("old"); 19 | assertEquals(2, p.getBuilders().size()); 20 | assertFalse(r.jenkins.getAdministrativeMonitor("OldData").isActivated()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | # More information about the Jenkins security scan can be found at the developer docs: https://www.jenkins.io/redirect/jenkins-security-scan/ 2 | 3 | name: jenkins-security-scan 4 | on: 5 | push: 6 | branches: 7 | - "main" 8 | pull_request: 9 | types: [ opened, synchronize, reopened ] 10 | workflow_dispatch: 11 | 12 | permissions: 13 | security-events: write 14 | contents: read 15 | actions: read 16 | 17 | jobs: 18 | security-scan: 19 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 20 | with: 21 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 22 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 23 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/jobdsl/context/ExtraVarsContext.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible.jobdsl.context; 2 | 3 | import hudson.util.Secret; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import javaposse.jobdsl.dsl.Context; 7 | import org.jenkinsci.plugins.ansible.ExtraVar; 8 | 9 | /** 10 | * @author pawbur (Pawel Burchard) 11 | */ 12 | public class ExtraVarsContext implements Context { 13 | private List extraVars = new ArrayList(); 14 | 15 | public void extraVar(String key, String value, boolean hidden) { 16 | ExtraVar extraVar = new ExtraVar(); 17 | extraVar.setKey(key); 18 | extraVar.setSecretValue(Secret.fromString(value)); 19 | extraVar.setHidden(hidden); 20 | this.extraVars.add(extraVar); 21 | } 22 | 23 | public List getExtraVars() { 24 | return extraVars; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/workflow/AnsibleVaultStep/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 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2023 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /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"), 23 | ANSIBLE_PLAYBOOK("ansible-playbook"), 24 | ANSIBLE_VAULT("ansible-vault"); 25 | 26 | private final String name; 27 | 28 | private AnsibleCommand(String name) { 29 | this.name = name; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/resources/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jenkins/ssh-agent:5.46.0-jdk21 2 | 3 | # Define build argument for ansible-core version 4 | ARG ANSIBLE_CORE_VERSION= 5 | 6 | # sshpass used by the plugin 7 | RUN apt-get update && \ 8 | apt-get install -y python3 python3-pip sshpass && \ 9 | rm -rf /var/lib/apt/lists/* 10 | 11 | # Install ansible 12 | RUN pip3 install --break-system-packages ansible-core==${ANSIBLE_CORE_VERSION} 13 | 14 | ENV PATH="${PATH}:/root/.local/bin" 15 | 16 | # Create ansible files 17 | RUN mkdir -p /ansible && \ 18 | mkdir -p /etc/ansible && \ 19 | mkdir -p /ansible/tmp && \ 20 | chmod 1777 /ansible/tmp && \ 21 | echo "---\nall:\n hosts:\n local:\n ansible_connection: local" > /ansible/inventory.yml && \ 22 | echo "---\n- hosts: local\n tasks:\n - debug:\n msg: 'Hello World'" > /ansible/playbook.yml && \ 23 | echo "[defaults]\ninventory = /ansible/inventory.yml" > /etc/ansible/ansible.cfg 24 | 25 | # Test only. Safe to connect with user/password 26 | RUN echo "password\npassword" | passwd root 27 | RUN sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config 28 | RUN sed -i 's/PermitRootLogin no/PermitRootLogin yes/g' /etc/ssh/sshd_config 29 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/AnsibleVaultBuilder/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 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/InventoryDoNotSpecify.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 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.TaskListener; 22 | import hudson.util.ArgumentListBuilder; 23 | import org.kohsuke.stapler.DataBoundConstructor; 24 | 25 | /** 26 | * Path to a file containing an Ansible inventory 27 | */ 28 | public class InventoryDoNotSpecify extends Inventory { 29 | @DataBoundConstructor 30 | public InventoryDoNotSpecify() {} 31 | 32 | @Override 33 | protected InventoryHandler getHandler() { 34 | return new InventoryHandler() { 35 | public void addArgument( 36 | ArgumentListBuilder args, FilePath workspace, EnvVars envVars, TaskListener listener) { 37 | // Do nothing 38 | } 39 | 40 | public void tearDown(TaskListener listener) {} 41 | }; 42 | } 43 | 44 | @Extension 45 | public static class DescriptorImpl extends InventoryDescriptor { 46 | 47 | @Override 48 | public String getDisplayName() { 49 | return "Do not specify Inventory"; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/CLIRunner.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible; 2 | 3 | import hudson.FilePath; 4 | import hudson.Launcher; 5 | import hudson.model.AbstractBuild; 6 | import hudson.model.BuildListener; 7 | import hudson.model.Run; 8 | import hudson.model.TaskListener; 9 | import hudson.util.ArgumentListBuilder; 10 | import java.io.IOException; 11 | import java.io.PrintStream; 12 | import java.util.Map; 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 | private final Launcher launcher; 23 | private final Run build; 24 | private final TaskListener listener; 25 | private final FilePath ws; 26 | 27 | public CLIRunner(AbstractBuild build, Launcher launcher, BuildListener listener) { 28 | this.launcher = launcher; 29 | this.build = build; 30 | this.listener = listener; 31 | this.ws = build.getWorkspace(); 32 | } 33 | 34 | public CLIRunner(Run build, FilePath ws, Launcher launcher, TaskListener listener) { 35 | this.launcher = launcher; 36 | this.build = build; 37 | this.listener = listener; 38 | this.ws = ws; 39 | } 40 | 41 | public boolean execute(ArgumentListBuilder args, Map environment) 42 | throws IOException, InterruptedException { 43 | PrintStream logger = listener.getLogger(); 44 | try { 45 | this.ws.mkdirs(); 46 | return launcher.launch() 47 | .pwd(ws) 48 | .envs(environment) 49 | .cmds(args) 50 | .stdout(logger) 51 | .stderr(logger) 52 | .join() 53 | == 0; 54 | } finally { 55 | logger.flush(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /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.TaskListener; 22 | import hudson.util.ArgumentListBuilder; 23 | import org.apache.commons.lang3.StringUtils; 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 | public final String path; 31 | 32 | @DataBoundConstructor 33 | public InventoryPath(String path) { 34 | this.path = path; 35 | } 36 | 37 | @Override 38 | protected InventoryHandler getHandler() { 39 | return new InventoryHandler() { 40 | public void addArgument( 41 | ArgumentListBuilder args, FilePath workspace, EnvVars envVars, TaskListener listener) { 42 | String expandedPath = envVars.expand(InventoryPath.this.path); 43 | if (StringUtils.isNotEmpty(expandedPath)) { 44 | args.add("-i").add(expandedPath); 45 | } 46 | } 47 | 48 | public void tearDown(TaskListener listener) {} 49 | }; 50 | } 51 | 52 | @Extension 53 | public static class DescriptorImpl extends InventoryDescriptor { 54 | 55 | @Override 56 | public String getDisplayName() { 57 | return "File or host list"; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /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 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/ansible/CompatibilityTest/jobs/old/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | true 8 | false 9 | false 10 | false 11 | 12 | false 13 | 14 | 15 | playbook.yml 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | false 24 | 25 | 5 26 | true 27 | false 28 | false 29 | 30 | false 31 | 32 | 33 | 34 | 35 | 36 | one.example.com 37 | 38 | 39 | false 40 | 41 | 5 42 | true 43 | false 44 | true 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /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 hudson.EnvVars; 19 | import hudson.FilePath; 20 | import hudson.model.Describable; 21 | import hudson.model.Descriptor; 22 | import hudson.model.TaskListener; 23 | import hudson.util.ArgumentListBuilder; 24 | import java.io.IOException; 25 | import jenkins.model.Jenkins; 26 | 27 | /** 28 | * Common Ansible inventory. 29 | */ 30 | public abstract class Inventory implements Describable { 31 | /** 32 | * @see hudson.model.Describable#getDescriptor() 33 | */ 34 | @SuppressWarnings("unchecked") 35 | public Descriptor getDescriptor() { 36 | return Jenkins.getActiveInstance().getDescriptorOrDie(getClass()); 37 | } 38 | 39 | protected abstract InventoryHandler getHandler(); 40 | 41 | public void addArgument(ArgumentListBuilder args, FilePath workspace, EnvVars envVars, TaskListener listener) 42 | throws InterruptedException, IOException { 43 | getHandler().addArgument(args, workspace, envVars, listener); 44 | } 45 | 46 | public void tearDown(TaskListener listener) throws InterruptedException, IOException { 47 | getHandler().tearDown(listener); 48 | } 49 | 50 | public abstract static class InventoryDescriptor extends Descriptor {} 51 | 52 | protected static interface InventoryHandler { 53 | 54 | void addArgument(ArgumentListBuilder args, FilePath workspace, EnvVars envVars, TaskListener listener) 55 | throws InterruptedException, IOException; 56 | 57 | void tearDown(TaskListener listener) throws InterruptedException, IOException; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/ExtraVar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 19 | import hudson.Extension; 20 | import hudson.model.AbstractDescribableImpl; 21 | import hudson.model.Descriptor; 22 | import hudson.util.Secret; 23 | import org.kohsuke.stapler.DataBoundConstructor; 24 | import org.kohsuke.stapler.DataBoundSetter; 25 | 26 | public class ExtraVar extends AbstractDescribableImpl { 27 | 28 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 29 | public String key; 30 | 31 | public transient String value; 32 | 33 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 34 | public Secret secretValue; 35 | 36 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 37 | public boolean hidden = true; 38 | 39 | @DataBoundConstructor 40 | public ExtraVar() {} 41 | 42 | protected Object readResolve() { 43 | if (value != null) { 44 | this.setSecretValue(Secret.fromString(value)); 45 | } 46 | return this; 47 | } 48 | 49 | @DataBoundSetter 50 | public void setKey(String key) { 51 | this.key = key; 52 | } 53 | 54 | @DataBoundSetter 55 | public void setHidden(boolean hidden) { 56 | this.hidden = hidden; 57 | } 58 | 59 | @DataBoundSetter 60 | public void setSecretValue(Secret value) { 61 | this.secretValue = value; 62 | } 63 | 64 | public String getKey() { 65 | return key; 66 | } 67 | 68 | public Secret getSecretValue() { 69 | return this.secretValue; 70 | } 71 | 72 | public boolean isHidden() { 73 | return hidden; 74 | } 75 | 76 | @Extension 77 | public static class DescriptorImpl extends Descriptor { 78 | 79 | public String getDisplayName() { 80 | return ""; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ansible/workflow/AnsibleAdHocStep/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 | 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 |
-------------------------------------------------------------------------------- /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 hudson.EnvVars; 19 | import hudson.Extension; 20 | import hudson.FilePath; 21 | import hudson.model.TaskListener; 22 | import hudson.util.ArgumentListBuilder; 23 | import java.io.IOException; 24 | import org.kohsuke.stapler.DataBoundConstructor; 25 | 26 | /** 27 | * Inline content for Ansible inventory. Inventory may be dynamic or not. 28 | */ 29 | public class InventoryContent extends Inventory { 30 | public final String content; 31 | public final boolean dynamic; 32 | 33 | private transient FilePath inventory = null; 34 | 35 | @DataBoundConstructor 36 | public InventoryContent(String content, boolean dynamic) { 37 | this.content = content; 38 | this.dynamic = dynamic; 39 | } 40 | 41 | @Override 42 | protected InventoryHandler getHandler() { 43 | return new InventoryHandler() { 44 | public void addArgument( 45 | ArgumentListBuilder args, FilePath workspace, EnvVars envVars, TaskListener listener) 46 | throws InterruptedException, IOException { 47 | inventory = createInventoryFile(inventory, workspace, envVars.expand(content)); 48 | args.add("-i").add(inventory); 49 | } 50 | 51 | public void tearDown(TaskListener listener) throws InterruptedException, IOException { 52 | Utils.deleteTempFile(inventory, listener); 53 | } 54 | 55 | private FilePath createInventoryFile(FilePath inventory, FilePath workspace, String content) 56 | throws IOException, InterruptedException { 57 | inventory = workspace.createTextTempFile("inventory", ".ini", content, false); 58 | inventory.chmod(dynamic ? 0500 : 0400); 59 | return inventory; 60 | } 61 | }; 62 | } 63 | 64 | @Extension 65 | public static class DescriptorImpl extends InventoryDescriptor { 66 | 67 | @Override 68 | public String getDisplayName() { 69 | return "Inline content"; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /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 hudson.EnvVars; 19 | import hudson.FilePath; 20 | import hudson.model.AbstractBuild; 21 | import hudson.model.BuildListener; 22 | import hudson.model.Run; 23 | import hudson.model.TaskListener; 24 | import hudson.util.ArgumentListBuilder; 25 | import java.io.IOException; 26 | 27 | /** 28 | * Invoke the ansible command 29 | */ 30 | public class AnsibleAdHocCommandInvocation extends AbstractAnsibleInvocation { 31 | 32 | private String module; 33 | private String hostPattern; 34 | private String command; 35 | 36 | protected AnsibleAdHocCommandInvocation(String exe, AbstractBuild build, BuildListener listener) 37 | throws IOException, InterruptedException, AnsibleInvocationException { 38 | super(exe, build, build.getWorkspace(), listener, build.getEnvironment(listener)); 39 | } 40 | 41 | public AnsibleAdHocCommandInvocation( 42 | String exe, Run build, FilePath ws, TaskListener listener, EnvVars envVars) 43 | throws IOException, InterruptedException, AnsibleInvocationException { 44 | super(exe, build, ws, listener, envVars); 45 | } 46 | 47 | public AnsibleAdHocCommandInvocation setHostPattern(String hostPattern) { 48 | this.hostPattern = hostPattern; 49 | return this; 50 | } 51 | 52 | private ArgumentListBuilder appendHostPattern(ArgumentListBuilder args) { 53 | args.add(envVars.expand(hostPattern)); 54 | return args; 55 | } 56 | 57 | public AnsibleAdHocCommandInvocation setModule(String module) { 58 | this.module = module; 59 | return this; 60 | } 61 | 62 | private ArgumentListBuilder appendModule(ArgumentListBuilder args) { 63 | addOptionAndValue(args, "-m", module); 64 | return args; 65 | } 66 | 67 | public AnsibleAdHocCommandInvocation setModuleCommand(String command) { 68 | this.command = command; 69 | return this; 70 | } 71 | 72 | public ArgumentListBuilder appendModuleCommand(ArgumentListBuilder args) { 73 | addOptionAndValue(args, "-a", command); 74 | return args; 75 | } 76 | 77 | @Override 78 | protected ArgumentListBuilder buildCommandLine() 79 | throws InterruptedException, AnsibleInvocationException, IOException { 80 | ArgumentListBuilder args = new ArgumentListBuilder(); 81 | prependPasswordCredentials(args); 82 | appendExecutable(args); 83 | appendHostPattern(args); 84 | appendInventory(args); 85 | appendModule(args); 86 | appendModuleCommand(args); 87 | appendBecome(args); 88 | appendSudo(args); 89 | appendForks(args); 90 | appendCredentials(args); 91 | appendVaultPasswordFile(args); 92 | appendExtraVars(args); 93 | appendAdditionalParameters(args); 94 | return args; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /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 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
86 | 87 |
88 |
89 |
90 |
91 |
92 | 93 | 94 | 95 | 96 | 97 |
98 | 99 |
100 | -------------------------------------------------------------------------------- /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 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |
99 | 100 |
101 |
102 |
103 |
104 |
105 | 106 | 107 | 108 | 109 | 110 |
111 | 112 |
113 | -------------------------------------------------------------------------------- /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.AnsibleVaultBuilder; 10 | import org.jenkinsci.plugins.ansible.jobdsl.context.AnsibleContext; 11 | 12 | /** 13 | * @author lanwen (Merkushev Kirill) 14 | */ 15 | @Extension(optional = true) 16 | public class AnsibleJobDslExtension extends ContextExtensionPoint { 17 | 18 | @DslExtensionMethod(context = StepContext.class) 19 | public Object ansibleAdHoc(String module, String command, Runnable closure) { 20 | AnsibleContext context = new AnsibleContext(); 21 | executeInContext(closure, context); 22 | 23 | AnsibleAdHocCommandBuilder adhoc = 24 | new AnsibleAdHocCommandBuilder(context.getHostPattern(), context.getInventory(), module, command); 25 | 26 | adhoc.setAdditionalParameters(context.getAdditionalParameters()); 27 | adhoc.setAnsibleName(context.getAnsibleName()); 28 | adhoc.setCredentialsId(context.getCredentialsId()); 29 | adhoc.setVaultCredentialsId(context.getVaultCredentialsId()); 30 | adhoc.setVaultTmpPath(context.getVaultTmpPath()); 31 | adhoc.setColorizedOutput(context.isColorizedOutput()); 32 | adhoc.setForks(context.getForks()); 33 | adhoc.setDisableHostKeyChecking(context.isDisableHostKeyChecking()); 34 | adhoc.setBecome(context.isBecome()); 35 | adhoc.setBecomeUser(context.getBecomeUser()); 36 | adhoc.setSudo(context.isSudo()); 37 | adhoc.setSudoUser(context.getSudoUser()); 38 | adhoc.setUnbufferedOutput(context.isUnbufferedOutput()); 39 | adhoc.setExtraVars(context.getExtraVars()); 40 | 41 | return adhoc; 42 | } 43 | 44 | @DslExtensionMethod(context = StepContext.class) 45 | public Object ansiblePlaybook(String playbook, Runnable closure) { 46 | AnsibleContext context = new AnsibleContext(); 47 | executeInContext(closure, context); 48 | 49 | AnsiblePlaybookBuilder plbook = new AnsiblePlaybookBuilder(playbook, context.getInventory()); 50 | 51 | plbook.setAdditionalParameters(context.getAdditionalParameters()); 52 | plbook.setAnsibleName(context.getAnsibleName()); 53 | plbook.setCredentialsId(context.getCredentialsId()); 54 | plbook.setVaultCredentialsId(context.getVaultCredentialsId()); 55 | plbook.setVaultTmpPath(context.getVaultTmpPath()); 56 | plbook.setColorizedOutput(context.isColorizedOutput()); 57 | plbook.setForks(context.getForks()); 58 | plbook.setDisableHostKeyChecking(context.isDisableHostKeyChecking()); 59 | plbook.setBecome(context.isBecome()); 60 | plbook.setBecomeUser(context.getBecomeUser()); 61 | plbook.setCheckMode(context.isCheckMode()); 62 | plbook.setSudo(context.isSudo()); 63 | plbook.setSudoUser(context.getSudoUser()); 64 | plbook.setUnbufferedOutput(context.isUnbufferedOutput()); 65 | plbook.setLimit(context.getLimit()); 66 | plbook.setTags(context.getTags()); 67 | plbook.setSkippedTags(context.getSkippedTags()); 68 | plbook.setStartAtTask(context.getStartAtTask()); 69 | plbook.setExtraVars(context.getExtraVars()); 70 | 71 | return plbook; 72 | } 73 | 74 | @DslExtensionMethod(context = StepContext.class) 75 | public Object ansibleVault(Runnable closure) { 76 | AnsibleContext context = new AnsibleContext(); 77 | executeInContext(closure, context); 78 | 79 | AnsibleVaultBuilder vault = new AnsibleVaultBuilder(); 80 | 81 | vault.setAnsibleName(context.getAnsibleName()); 82 | vault.setAction(context.getAction()); 83 | vault.setVaultCredentialsId(context.getVaultCredentialsId()); 84 | vault.setNewVaultCredentialsId(context.getNewVaultCredentialsId()); 85 | vault.setVaultTmpPath(context.getVaultTmpPath()); 86 | vault.setContent(context.getContent()); 87 | vault.setInput(context.getInput()); 88 | vault.setOutput(context.getOutput()); 89 | 90 | return vault; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /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 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.util.List; 25 | import org.jenkinsci.plugins.plaincredentials.FileCredentials; 26 | import org.jenkinsci.plugins.plaincredentials.StringCredentials; 27 | 28 | class Utils { 29 | /** 30 | * Copy the SSH private key into a temporary file. 31 | * 32 | * @param key the destination file 33 | * @param credentials the SSH key 34 | * @return the file 35 | * @throws IOException 36 | * @throws InterruptedException 37 | */ 38 | static FilePath createSshKeyFile(FilePath key, FilePath tmpPath, SSHUserPrivateKey credentials, boolean inThisDir) 39 | throws IOException, InterruptedException { 40 | StringBuilder sb = new StringBuilder(); 41 | List privateKeys = credentials.getPrivateKeys(); 42 | for (String s : privateKeys) { 43 | sb.append(s); 44 | } 45 | key = tmpPath.createTextTempFile("ssh", ".key", sb.toString(), inThisDir); 46 | key.chmod(0400); 47 | return key; 48 | } 49 | 50 | static FilePath createSshAskPassFile( 51 | FilePath script, FilePath tmpPath, SSHUserPrivateKey credentials, boolean inThisDir) 52 | throws IOException, InterruptedException { 53 | tmpPath.mkdirs(); 54 | StringBuilder sb = new StringBuilder(); 55 | sb.append("#! /bin/sh\n").append("/bin/echo \"" + Secret.toString(credentials.getPassphrase()) + "\""); 56 | script = tmpPath.createTextTempFile("ssh", ".sh", sb.toString(), inThisDir); 57 | script.chmod(0700); 58 | return script; 59 | } 60 | 61 | /** 62 | * Copy the Vault password into a temporary file. 63 | * 64 | * @param key the destination file 65 | * @param credentials the SSH key 66 | * @return the file 67 | * @throws IOException 68 | * @throws InterruptedException 69 | */ 70 | static FilePath createVaultPasswordFile(FilePath key, FilePath tmpPath, FileCredentials credentials) 71 | throws IOException, InterruptedException { 72 | try (InputStream content = credentials.getContent()) { 73 | tmpPath.mkdirs(); 74 | key = tmpPath.createTempFile("vault", ".password"); 75 | key.copyFrom(content); 76 | key.chmod(0400); 77 | } 78 | return key; 79 | } 80 | 81 | /** 82 | * Copy the Vault password into a temporary file. 83 | * 84 | * @param key the destination file 85 | * @param credentials the SSH key 86 | * @return the file 87 | * @throws IOException 88 | * @throws InterruptedException 89 | */ 90 | static FilePath createVaultPasswordFile(FilePath key, FilePath tmpPath, StringCredentials credentials) 91 | throws IOException, InterruptedException { 92 | tmpPath.mkdirs(); 93 | key = tmpPath.createTextTempFile( 94 | "vault", ".password", credentials.getSecret().getPlainText(), true); 95 | key.chmod(0400); 96 | return key; 97 | } 98 | 99 | /** 100 | * Delete a temporary file. Print a warning in the log when deletion fails. 101 | * 102 | * @param tempFile the file to be removed 103 | * @param listener the build listener 104 | */ 105 | static void deleteTempFile(FilePath tempFile, TaskListener listener) throws IOException, InterruptedException { 106 | if (tempFile != null) { 107 | try { 108 | tempFile.delete(); 109 | } catch (IOException ioe) { 110 | if (tempFile.exists()) { 111 | listener.getLogger().println("[WARNING] temp file " + tempFile + " not deleted"); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AnsiblePlaybookInvocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 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.FilePath; 20 | import hudson.model.AbstractBuild; 21 | import hudson.model.BuildListener; 22 | import hudson.model.Run; 23 | import hudson.model.TaskListener; 24 | import hudson.util.ArgumentListBuilder; 25 | import java.io.IOException; 26 | 27 | /** 28 | * Invoke the ansible-playbook command 29 | */ 30 | public class AnsiblePlaybookInvocation extends AbstractAnsibleInvocation { 31 | 32 | private String playbook; 33 | private String limit; 34 | private String tags; 35 | private String skippedTags; 36 | private String startAtTask; 37 | private boolean checkMode; 38 | 39 | protected AnsiblePlaybookInvocation(String exe, AbstractBuild build, BuildListener listener, EnvVars envVars) 40 | throws IOException, InterruptedException, AnsibleInvocationException { 41 | this(exe, build, build.getWorkspace(), listener, envVars); 42 | } 43 | 44 | public AnsiblePlaybookInvocation(String exe, Run build, FilePath ws, TaskListener listener, EnvVars envVars) 45 | throws IOException, InterruptedException, AnsibleInvocationException { 46 | super(exe, build, ws, listener, envVars); 47 | } 48 | 49 | public AnsiblePlaybookInvocation setPlaybook(String playbook) { 50 | this.playbook = playbook; 51 | return this; 52 | } 53 | 54 | public AnsiblePlaybookInvocation setCheckMode(boolean checkMode) { 55 | this.checkMode = checkMode; 56 | return this; 57 | } 58 | 59 | private ArgumentListBuilder appendPlaybook(ArgumentListBuilder args) { 60 | args.add(envVars.expand(playbook)); 61 | return args; 62 | } 63 | 64 | public AnsiblePlaybookInvocation setLimit(String limit) { 65 | this.limit = limit; 66 | return this; 67 | } 68 | 69 | private ArgumentListBuilder appendLimit(ArgumentListBuilder args) { 70 | addOptionAndValue(args, "-l", limit); 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 | addOptionAndValue(args, "-t", tags); 81 | return args; 82 | } 83 | 84 | public AnsiblePlaybookInvocation setSkippedTags(String skippedTags) { 85 | this.skippedTags = skippedTags; 86 | return this; 87 | } 88 | 89 | private ArgumentListBuilder appendSkippedTags(ArgumentListBuilder args) { 90 | addKeyValuePair(args, "--skip-tags", skippedTags); 91 | return args; 92 | } 93 | 94 | public AnsiblePlaybookInvocation setStartTask(String startAtTask) { 95 | this.startAtTask = startAtTask; 96 | return this; 97 | } 98 | 99 | private ArgumentListBuilder appendStartTask(ArgumentListBuilder args) { 100 | addKeyValuePair(args, "--start-at-task", startAtTask); 101 | return args; 102 | } 103 | 104 | protected ArgumentListBuilder appendCheckMode(ArgumentListBuilder args) { 105 | if (checkMode) { 106 | args.add("--check"); 107 | } 108 | return args; 109 | } 110 | 111 | @Override 112 | protected ArgumentListBuilder buildCommandLine() 113 | throws InterruptedException, AnsibleInvocationException, IOException { 114 | ArgumentListBuilder args = new ArgumentListBuilder(); 115 | prependPasswordCredentials(args); 116 | appendExecutable(args); 117 | appendPlaybook(args); 118 | appendInventory(args); 119 | appendLimit(args); 120 | appendTags(args); 121 | appendSkippedTags(args); 122 | appendStartTask(args); 123 | appendBecome(args); 124 | appendCheckMode(args); 125 | appendSudo(args); 126 | appendForks(args); 127 | appendCredentials(args); 128 | appendVaultPasswordFile(args); 129 | appendExtraVars(args); 130 | appendAdditionalParameters(args); 131 | return args; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AbstractAnsibleBuilderDescriptor.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible; 2 | 3 | import static com.cloudbees.plugins.credentials.CredentialsMatchers.anyOf; 4 | import static com.cloudbees.plugins.credentials.CredentialsMatchers.instanceOf; 5 | 6 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 7 | import com.cloudbees.plugins.credentials.CredentialsProvider; 8 | import com.cloudbees.plugins.credentials.common.StandardCredentials; 9 | import com.cloudbees.plugins.credentials.common.StandardListBoxModel; 10 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 11 | import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; 12 | import hudson.model.AbstractProject; 13 | import hudson.model.Item; 14 | import hudson.tasks.BuildStepDescriptor; 15 | import hudson.tasks.Builder; 16 | import hudson.util.FormValidation; 17 | import hudson.util.ListBoxModel; 18 | import java.util.List; 19 | import jenkins.model.Jenkins; 20 | import org.apache.commons.lang3.StringUtils; 21 | import org.jenkinsci.plugins.ansible.Inventory.InventoryDescriptor; 22 | import org.jenkinsci.plugins.plaincredentials.FileCredentials; 23 | import org.jenkinsci.plugins.plaincredentials.StringCredentials; 24 | import org.kohsuke.stapler.AncestorInPath; 25 | import org.kohsuke.stapler.QueryParameter; 26 | 27 | /** 28 | * Common descriptor for Ansible build steps 29 | */ 30 | public abstract class AbstractAnsibleBuilderDescriptor extends BuildStepDescriptor { 31 | private final String displayName; 32 | 33 | protected AbstractAnsibleBuilderDescriptor(String displayName) { 34 | this.displayName = displayName; 35 | load(); 36 | } 37 | 38 | protected FormValidation checkNotNullOrEmpty(String parameter, String errorMessage) { 39 | if (StringUtils.isNotBlank(parameter)) { 40 | return FormValidation.ok(); 41 | } else { 42 | return FormValidation.error(errorMessage); 43 | } 44 | } 45 | 46 | public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item item, @QueryParameter String credentialsId) { 47 | 48 | StandardListBoxModel result = new StandardListBoxModel(); 49 | if (item == null) { 50 | if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) { 51 | return result.includeCurrentValue(credentialsId); 52 | } 53 | } else { 54 | if (!item.hasPermission(Item.EXTENDED_READ) && !item.hasPermission(CredentialsProvider.USE_ITEM)) { 55 | return result.includeCurrentValue(credentialsId); 56 | } 57 | } 58 | 59 | return result.includeEmptyValue() 60 | .withMatching( 61 | anyOf(instanceOf(SSHUserPrivateKey.class), instanceOf(UsernamePasswordCredentials.class)), 62 | CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, item)) 63 | .includeCurrentValue(credentialsId); 64 | } 65 | 66 | public ListBoxModel doFillVaultCredentialsIdItems( 67 | @AncestorInPath Item item, @QueryParameter String vaultCredentialsId) { 68 | return fillVaultCredentials(item, vaultCredentialsId); 69 | } 70 | 71 | public ListBoxModel doFillNewVaultCredentialsIdItems( 72 | @AncestorInPath Item item, @QueryParameter String newVaultCredentialsId) { 73 | return fillVaultCredentials(item, newVaultCredentialsId); 74 | } 75 | 76 | private ListBoxModel fillVaultCredentials(Item item, String credentialsId) { 77 | StandardListBoxModel result = new StandardListBoxModel(); 78 | if (item == null) { 79 | if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) { 80 | return result.includeCurrentValue(credentialsId); 81 | } 82 | } else { 83 | if (!item.hasPermission(Item.EXTENDED_READ) && !item.hasPermission(CredentialsProvider.USE_ITEM)) { 84 | return result.includeCurrentValue(credentialsId); 85 | } 86 | } 87 | 88 | return result.includeEmptyValue() 89 | .withMatching( 90 | anyOf(instanceOf(FileCredentials.class), instanceOf(StringCredentials.class)), 91 | CredentialsProvider.lookupCredentials(StandardCredentials.class, item)) 92 | .includeCurrentValue(credentialsId); 93 | } 94 | 95 | public List getInventories() { 96 | return Jenkins.getActiveInstance().getDescriptorList(Inventory.class); 97 | } 98 | 99 | @Override 100 | public boolean isApplicable(Class klass) { 101 | return true; 102 | } 103 | 104 | @Override 105 | public String getDisplayName() { 106 | return displayName; 107 | } 108 | 109 | public AnsibleInstallation[] getInstallations() { 110 | return AnsibleInstallation.allInstallations(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ansible/AnsibleVaultInvocationTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.junit.jupiter.api.Assertions.assertThrows; 6 | import static org.mockito.Mockito.*; 7 | 8 | import hudson.AbortException; 9 | import hudson.EnvVars; 10 | import hudson.model.AbstractBuild; 11 | import hudson.model.BuildListener; 12 | import hudson.model.TaskListener; 13 | import hudson.util.ArgumentListBuilder; 14 | import org.junit.jupiter.api.Test; 15 | import org.mockito.ArgumentCaptor; 16 | 17 | /** 18 | * Test Vault invocations 19 | * 20 | * @author Michael Cresswell 21 | */ 22 | class AnsibleVaultInvocationTest { 23 | 24 | private static final String EXE = "ansible-vault"; 25 | 26 | @Test 27 | void shouldGenerateEncryptString() throws Exception { 28 | CLIRunner runner = mock(CLIRunner.class); 29 | AnsibleVaultInvocation invocation = getInvocation(); 30 | 31 | invocation.setAction("encrypt_string"); 32 | invocation.setContent("Test Content"); 33 | // When 34 | invocation.execute(runner); 35 | // Then 36 | ArgumentCaptor argument = ArgumentCaptor.forClass(ArgumentListBuilder.class); 37 | verify(runner).execute(argument.capture(), anyMap()); 38 | 39 | assertThat(argument.getValue().toString(), is("ansible-vault encrypt_string ******")); 40 | } 41 | 42 | @Test 43 | void shouldGenerateEncrypt() throws Exception { 44 | CLIRunner runner = mock(CLIRunner.class); 45 | AnsibleVaultInvocation invocation = getInvocation(); 46 | 47 | invocation.setAction("encrypt"); 48 | invocation.setInput("/tmp/my_var_file.yml"); 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(), is("ansible-vault encrypt /tmp/my_var_file.yml")); 55 | } 56 | 57 | @Test 58 | void shouldGenerateDecrypt() throws Exception { 59 | CLIRunner runner = mock(CLIRunner.class); 60 | AnsibleVaultInvocation invocation = getInvocation(); 61 | 62 | invocation.setAction("decrypt"); 63 | invocation.setInput("/tmp/my_var_file.yml"); 64 | // When 65 | invocation.execute(runner); 66 | // Then 67 | ArgumentCaptor argument = ArgumentCaptor.forClass(ArgumentListBuilder.class); 68 | verify(runner).execute(argument.capture(), anyMap()); 69 | assertThat(argument.getValue().toString(), is("ansible-vault decrypt /tmp/my_var_file.yml")); 70 | } 71 | 72 | @Test 73 | void shouldGenerateRekey() throws Exception { 74 | CLIRunner runner = mock(CLIRunner.class); 75 | AnsibleVaultInvocation invocation = getInvocation(); 76 | 77 | invocation.setAction("rekey"); 78 | invocation.setInput("/tmp/my_var_file.yml"); 79 | // When 80 | invocation.execute(runner); 81 | // Then 82 | ArgumentCaptor argument = ArgumentCaptor.forClass(ArgumentListBuilder.class); 83 | verify(runner).execute(argument.capture(), anyMap()); 84 | assertThat(argument.getValue().toString(), is("ansible-vault rekey /tmp/my_var_file.yml")); 85 | } 86 | 87 | @Test 88 | void shouldNotGenerateView() throws Exception { 89 | CLIRunner runner = mock(CLIRunner.class); 90 | AnsibleVaultInvocation invocation = getInvocation(); 91 | 92 | invocation.setAction("view"); 93 | invocation.setInput("/tmp/my_var_file.yml"); 94 | // When 95 | assertThrows(AbortException.class, () -> invocation.execute(runner)); 96 | // Then 97 | verifyNoInteractions(runner); 98 | } 99 | 100 | @Test 101 | void shouldNotGenerateEdit() throws Exception { 102 | CLIRunner runner = mock(CLIRunner.class); 103 | AnsibleVaultInvocation invocation = getInvocation(); 104 | 105 | invocation.setAction("edit"); 106 | invocation.setInput("/tmp/my_var_file.yml"); 107 | // When 108 | assertThrows(AbortException.class, () -> invocation.execute(runner)); 109 | // Then 110 | verifyNoInteractions(runner); 111 | } 112 | 113 | @Test 114 | void shouldNotGenerateCreate() throws Exception { 115 | CLIRunner runner = mock(CLIRunner.class); 116 | AnsibleVaultInvocation invocation = getInvocation(); 117 | 118 | invocation.setAction("edit"); 119 | invocation.setInput("/tmp/my_var_file.yml"); 120 | // When 121 | assertThrows(AbortException.class, () -> invocation.execute(runner)); 122 | // Then 123 | verifyNoInteractions(runner); 124 | } 125 | 126 | private AnsibleVaultInvocation getInvocation() throws Exception { 127 | // Given 128 | BuildListener listener = mock(BuildListener.class); 129 | AbstractBuild build = mock(AbstractBuild.class); 130 | when(build.getEnvironment(any(TaskListener.class))).thenReturn(new EnvVars()); 131 | return new AnsibleVaultInvocation(EXE, build, listener, new EnvVars()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /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 hudson.EnvVars; 19 | import hudson.Extension; 20 | import hudson.FilePath; 21 | import hudson.Util; 22 | import hudson.model.EnvironmentSpecific; 23 | import hudson.model.Node; 24 | import hudson.model.TaskListener; 25 | import hudson.slaves.NodeSpecific; 26 | import hudson.tools.ToolDescriptor; 27 | import hudson.tools.ToolInstallation; 28 | import hudson.tools.ToolProperty; 29 | import java.io.IOException; 30 | import java.io.Serializable; 31 | import java.util.List; 32 | import jenkins.model.Jenkins; 33 | import net.sf.json.JSONObject; 34 | import org.jenkinsci.Symbol; 35 | import org.kohsuke.stapler.DataBoundConstructor; 36 | import org.kohsuke.stapler.StaplerRequest2; 37 | 38 | /** 39 | * {@code ToolInstallation} for Ansible 40 | */ 41 | public class AnsibleInstallation extends ToolInstallation 42 | implements EnvironmentSpecific, NodeSpecific, Serializable { 43 | 44 | @DataBoundConstructor 45 | public AnsibleInstallation(String name, String home, List> properties) { 46 | super(name, home, properties); 47 | } 48 | 49 | public AnsibleInstallation forEnvironment(EnvVars environment) { 50 | return new AnsibleInstallation( 51 | getName(), environment.expand(getHome()), getProperties().toList()); 52 | } 53 | 54 | public AnsibleInstallation forNode(Node node, TaskListener log) throws IOException, InterruptedException { 55 | return new AnsibleInstallation( 56 | getName(), translateFor(node, log), getProperties().toList()); 57 | } 58 | 59 | public static String getExecutable( 60 | String name, AnsibleCommand command, Node node, TaskListener listener, EnvVars env) 61 | throws IOException, InterruptedException { 62 | if (name != null) { 63 | Jenkins j = Jenkins.getInstance(); 64 | if (j != null) { 65 | for (AnsibleInstallation tool : 66 | j.getDescriptorByType(DescriptorImpl.class).getInstallations()) { 67 | if (tool.getName().equals(name)) { 68 | if (node != null) { 69 | tool = tool.forNode(node, listener); 70 | } 71 | if (env != null) { 72 | tool = tool.forEnvironment(env); 73 | } 74 | String home = Util.fixEmpty(tool.getHome()); 75 | if (home != null) { 76 | if (node != null) { 77 | FilePath homePath = node.createPath(home); 78 | if (homePath != null) { 79 | return homePath.child(command.getName()).getRemote(); 80 | } 81 | } 82 | return home + "/" + command.getName(); 83 | } 84 | } 85 | } 86 | } 87 | } 88 | return command.getName(); 89 | } 90 | 91 | public static AnsibleInstallation[] allInstallations() { 92 | AnsibleInstallation.DescriptorImpl ansibleDescriptor = 93 | Jenkins.getActiveInstance().getDescriptorByType(AnsibleInstallation.DescriptorImpl.class); 94 | return ansibleDescriptor.getInstallations(); 95 | } 96 | 97 | public static AnsibleInstallation getInstallation(String ansibleInstallation) throws IOException { 98 | AnsibleInstallation[] installations = allInstallations(); 99 | if (ansibleInstallation == null) { 100 | if (installations.length == 0) { 101 | throw new IOException("Ansible not found"); 102 | } 103 | return installations[0]; 104 | } else { 105 | for (AnsibleInstallation installation : installations) { 106 | if (ansibleInstallation.equals(installation.getName())) { 107 | return installation; 108 | } 109 | } 110 | } 111 | throw new IOException("Ansible not found"); 112 | } 113 | 114 | @Override 115 | public void buildEnvVars(EnvVars env) { 116 | String home = Util.fixEmpty(getHome()); 117 | if (home != null) { 118 | env.put("PATH+ANSIBLE", home); 119 | } 120 | } 121 | 122 | @Extension 123 | @Symbol("ansible") 124 | public static class DescriptorImpl extends ToolDescriptor { 125 | 126 | public DescriptorImpl() { 127 | load(); 128 | } 129 | 130 | @Override 131 | public boolean configure(StaplerRequest2 req, JSONObject json) throws FormException { 132 | super.configure(req, json); 133 | save(); 134 | return true; 135 | } 136 | 137 | @Override 138 | public String getDisplayName() { 139 | return "Ansible"; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AnsibleVaultInvocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.jenkinsci.plugins.ansible; 15 | 16 | import com.cloudbees.plugins.credentials.common.StandardCredentials; 17 | import hudson.AbortException; 18 | import hudson.EnvVars; 19 | import hudson.FilePath; 20 | import hudson.model.AbstractBuild; 21 | import hudson.model.BuildListener; 22 | import hudson.model.Run; 23 | import hudson.model.TaskListener; 24 | import hudson.util.ArgumentListBuilder; 25 | import java.io.IOException; 26 | import org.jenkinsci.plugins.plaincredentials.FileCredentials; 27 | import org.jenkinsci.plugins.plaincredentials.StringCredentials; 28 | 29 | /** 30 | * Invoke the ansible-vault command 31 | * 32 | * @author Michael Cresswell 33 | */ 34 | public class AnsibleVaultInvocation extends AbstractAnsibleInvocation { 35 | 36 | private String action; 37 | private String content; 38 | private String input; 39 | private String output; 40 | private StandardCredentials newVaultCredentials; 41 | 42 | private FilePath newVaultPassword = null; 43 | 44 | private FilePath ws = null; 45 | 46 | protected AnsibleVaultInvocation(String exe, AbstractBuild build, BuildListener listener, EnvVars envVars) 47 | throws IOException, InterruptedException, AnsibleInvocationException { 48 | this(exe, build, build.getWorkspace(), listener, envVars); 49 | } 50 | 51 | public AnsibleVaultInvocation(String exe, Run build, FilePath ws, TaskListener listener, EnvVars envVars) 52 | throws IOException, InterruptedException, AnsibleInvocationException { 53 | super(exe, build, ws, listener, envVars); 54 | this.ws = ws; 55 | } 56 | 57 | public AnsibleVaultInvocation setAction(String action) { 58 | this.action = action; 59 | return this; 60 | } 61 | 62 | private ArgumentListBuilder appendAction(ArgumentListBuilder args) throws AbortException { 63 | if (!"edit".equals(action) && !"create".equals(action) && !"view".equals(action)) { 64 | args.add(action); 65 | } else { 66 | throw new AbortException(action 67 | + ": ansible-plugin does not support interactive vault actions such as create, edit, or view."); 68 | } 69 | return args; 70 | } 71 | 72 | public AnsibleVaultInvocation setContent(String content) { 73 | this.content = content; 74 | return this; 75 | } 76 | 77 | private ArgumentListBuilder appendContent(ArgumentListBuilder args) { 78 | if (content != null && !content.isEmpty()) { 79 | args.addMasked(content); 80 | } 81 | return args; 82 | } 83 | 84 | public AnsibleVaultInvocation setInput(String input) { 85 | this.input = input; 86 | return this; 87 | } 88 | 89 | private ArgumentListBuilder appendInput(ArgumentListBuilder args) { 90 | if (input != null && !input.isEmpty()) { 91 | args.add(input); 92 | } 93 | return args; 94 | } 95 | 96 | public AnsibleVaultInvocation setNewVaultCredentials(StandardCredentials newVaultCredentials) { 97 | this.newVaultCredentials = newVaultCredentials; 98 | return this; 99 | } 100 | 101 | protected ArgumentListBuilder appendNewVaultPasswordFile(ArgumentListBuilder args) 102 | throws IOException, InterruptedException { 103 | if (newVaultCredentials != null) { 104 | FilePath tmpPath = vaultTmpPath != null ? vaultTmpPath : ws; 105 | if (newVaultCredentials instanceof FileCredentials) { 106 | FileCredentials secretFile = (FileCredentials) newVaultCredentials; 107 | newVaultPassword = Utils.createVaultPasswordFile(newVaultPassword, tmpPath, secretFile); 108 | args.add("--new-vault-password-file") 109 | .add(newVaultPassword.getRemote().replace("%", "%%")); 110 | } else if (newVaultCredentials instanceof StringCredentials) { 111 | StringCredentials secretText = (StringCredentials) newVaultCredentials; 112 | newVaultPassword = Utils.createVaultPasswordFile(newVaultPassword, tmpPath, secretText); 113 | args.add("--new-vault-password-file") 114 | .add(newVaultPassword.getRemote().replace("%", "%%")); 115 | } 116 | } 117 | return args; 118 | } 119 | 120 | public AnsibleVaultInvocation setOutput(String output) { 121 | this.output = output; 122 | return this; 123 | } 124 | 125 | private ArgumentListBuilder appendOutput(ArgumentListBuilder args) { 126 | if (output != null && !output.isEmpty()) { 127 | args.add(output); 128 | } 129 | return args; 130 | } 131 | 132 | @Override 133 | protected ArgumentListBuilder buildCommandLine() 134 | throws InterruptedException, AnsibleInvocationException, IOException { 135 | ArgumentListBuilder args = new ArgumentListBuilder(); 136 | prependPasswordCredentials(args); 137 | appendExecutable(args); 138 | appendAction(args); 139 | appendVaultPasswordFile(args); 140 | appendNewVaultPasswordFile(args); 141 | appendOutput(args); 142 | appendContent(args); 143 | appendInput(args); 144 | return args; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.jenkins-ci.plugins 6 | plugin 7 | 5.28 8 | 9 | 10 | org.jenkins-ci.plugins 11 | ansible 12 | ${changelist} 13 | hpi 14 | Jenkins Ansible plugin 15 | Ansible support in Jenkins 16 | 17 | 18 | MIT License 19 | https://opensource.org/license/mit/ 20 | 21 | 22 | 23 | scm:git:https://github.com/${gitHubRepo}.git 24 | scm:git:https@github.com:${gitHubRepo}.git 25 | ${scmTag} 26 | https://github.com/${gitHubRepo} 27 | 28 | 29 | 999999-SNAPSHOT 30 | jenkinsci/ansible-plugin 31 | 32 | 2.492 33 | ${jenkins.baseline}.3 34 | false 35 | false 36 | 37 | 38 | 1.21.3 39 | 40 | 41 | 42 | 43 | 44 | io.jenkins.tools.bom 45 | bom-${jenkins.baseline}.x 46 | 5473.vb_9533d9e5d88 47 | pom 48 | import 49 | 50 | 51 | 52 | 53 | 54 | io.jenkins.plugins 55 | commons-lang3-api 56 | 57 | 58 | org.jenkins-ci 59 | symbol-annotation 60 | true 61 | 62 | 63 | org.jenkins-ci.plugins 64 | credentials 65 | 66 | 67 | org.jenkins-ci.plugins 68 | job-dsl 69 | true 70 | 71 | 72 | org.jenkins-ci.plugins 73 | plain-credentials 74 | 75 | 76 | org.jenkins-ci.plugins 77 | ssh-credentials 78 | 79 | 80 | 81 | org.jenkins-ci.plugins.workflow 82 | workflow-step-api 83 | true 84 | 85 | 86 | junit 87 | junit 88 | test 89 | 90 | 91 | org.jenkins-ci.plugins 92 | pipeline-utility-steps 93 | test 94 | 95 | 96 | org.jenkins-ci.plugins 97 | ssh-slaves 98 | test 99 | 100 | 101 | org.jenkins-ci.plugins.workflow 102 | workflow-cps 103 | test 104 | 105 | 106 | org.jenkins-ci.plugins.workflow 107 | workflow-job 108 | test 109 | 110 | 111 | org.jenkinsci.plugins 112 | pipeline-model-definition 113 | test 114 | 115 | 116 | org.mockito 117 | mockito-core 118 | test 119 | 120 | 121 | org.testcontainers 122 | junit-jupiter 123 | ${testcontainer.version} 124 | test 125 | 126 | 127 | org.testcontainers 128 | testcontainers 129 | ${testcontainer.version} 130 | test 131 | 132 | 133 | org.apache.commons 134 | commons-compress 135 | 136 | 137 | org.jetbrains 138 | annotations 139 | 140 | 141 | org.slf4j 142 | slf4j-api 143 | 144 | 145 | 146 | 147 | 148 | 149 | repo.jenkins-ci.org 150 | https://repo.jenkins-ci.org/public/ 151 | 152 | 153 | 154 | 155 | repo.jenkins-ci.org 156 | https://repo.jenkins-ci.org/public/ 157 | 158 | 159 | 160 | 161 | 162 | org.apache.maven.plugins 163 | maven-surefire-plugin 164 | 165 | all 166 | true 167 | 1C 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandInvocationTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.hamcrest.Matchers.aMapWithSize; 6 | import static org.hamcrest.Matchers.hasEntry; 7 | import static org.mockito.Mockito.*; 8 | 9 | import hudson.EnvVars; 10 | import hudson.model.AbstractBuild; 11 | import hudson.model.BuildListener; 12 | import hudson.model.TaskListener; 13 | import hudson.util.ArgumentListBuilder; 14 | import java.util.Map; 15 | import org.junit.jupiter.api.Test; 16 | import org.mockito.ArgumentCaptor; 17 | 18 | /** 19 | * Created with IntelliJ IDEA. 20 | * User: jcsirot 21 | * Date: 22/05/15 22 | * Time: 19:30 23 | * To change this template use File | Settings | File Templates. 24 | */ 25 | class AnsibleAdHocCommandInvocationTest { 26 | 27 | @Test 28 | void should_generate_simple_invocation() throws Exception { 29 | // Given 30 | Inventory inventory = new InventoryPath("/tmp/hosts"); 31 | BuildListener listener = mock(BuildListener.class); 32 | CLIRunner runner = mock(CLIRunner.class); 33 | AbstractBuild build = mock(AbstractBuild.class); 34 | when(build.getEnvironment(any(TaskListener.class))).thenReturn(new EnvVars()); 35 | AnsibleAdHocCommandInvocation invocation = 36 | new AnsibleAdHocCommandInvocation("/usr/local/bin/ansible", build, listener); 37 | invocation.setHostPattern("localhost"); 38 | invocation.setInventory(inventory); 39 | invocation.setModule("ping"); 40 | invocation.setForks(5); 41 | // When 42 | invocation.execute(runner); 43 | // Then 44 | ArgumentCaptor argument = ArgumentCaptor.forClass(ArgumentListBuilder.class); 45 | verify(runner).execute(argument.capture(), anyMap()); 46 | assertThat(argument.getValue().toString(), is("/usr/local/bin/ansible localhost -i /tmp/hosts -m ping -f 5")); 47 | } 48 | 49 | @Test 50 | void should_generate_no_forks() throws Exception { 51 | // Given 52 | Inventory inventory = new InventoryPath("/tmp/hosts"); 53 | BuildListener listener = mock(BuildListener.class); 54 | CLIRunner runner = mock(CLIRunner.class); 55 | AbstractBuild build = mock(AbstractBuild.class); 56 | when(build.getEnvironment(any(TaskListener.class))).thenReturn(new EnvVars()); 57 | AnsibleAdHocCommandInvocation invocation = 58 | new AnsibleAdHocCommandInvocation("/usr/local/bin/ansible", build, listener); 59 | invocation.setHostPattern("localhost"); 60 | invocation.setInventory(inventory); 61 | invocation.setModule("ping"); 62 | invocation.setForks(0); 63 | // When 64 | invocation.execute(runner); 65 | // Then 66 | ArgumentCaptor argument = ArgumentCaptor.forClass(ArgumentListBuilder.class); 67 | verify(runner).execute(argument.capture(), anyMap()); 68 | assertThat(argument.getValue().toString(), is("/usr/local/bin/ansible localhost -i /tmp/hosts -m ping")); 69 | } 70 | 71 | @Test 72 | void should_generate_simple_invocation_with_env() throws Exception { 73 | // Given 74 | Inventory inventory = new InventoryPath("/tmp/hosts"); 75 | BuildListener listener = mock(BuildListener.class); 76 | CLIRunner runner = mock(CLIRunner.class); 77 | AbstractBuild build = mock(AbstractBuild.class); 78 | when(build.getEnvironment(any(TaskListener.class))).thenReturn(new EnvVars()); 79 | AnsibleAdHocCommandInvocation invocation = 80 | new AnsibleAdHocCommandInvocation("/usr/local/bin/ansible", build, listener); 81 | invocation.setHostPattern("localhost"); 82 | invocation.setInventory(inventory); 83 | invocation.setModule("ping"); 84 | invocation.setForks(5); 85 | invocation.setColorizedOutput(true); 86 | invocation.setDisableHostKeyCheck(true); 87 | invocation.setUnbufferedOutput(true); 88 | // When 89 | invocation.execute(runner); 90 | // Then 91 | ArgumentCaptor> argument = ArgumentCaptor.forClass(Map.class); 92 | verify(runner).execute(any(ArgumentListBuilder.class), argument.capture()); 93 | 94 | assertThat(argument.getValue(), hasEntry("PYTHONUNBUFFERED", "1")); 95 | assertThat(argument.getValue(), hasEntry("ANSIBLE_FORCE_COLOR", "true")); 96 | assertThat(argument.getValue(), hasEntry("ANSIBLE_HOST_KEY_CHECKING", "False")); 97 | } 98 | 99 | @Test 100 | void secure_by_default_SEC_630() throws Exception { 101 | // Given 102 | Inventory inventory = new InventoryPath("/tmp/hosts"); 103 | BuildListener listener = mock(BuildListener.class); 104 | CLIRunner runner = mock(CLIRunner.class); 105 | AbstractBuild build = mock(AbstractBuild.class); 106 | when(build.getEnvironment(any(TaskListener.class))).thenReturn(new EnvVars()); 107 | AnsibleAdHocCommandInvocation invocation = 108 | new AnsibleAdHocCommandInvocation("/usr/local/bin/ansible", build, listener); 109 | invocation.setHostPattern("localhost"); 110 | invocation.setInventory(inventory); 111 | invocation.setModule("ping"); 112 | invocation.setForks(5); 113 | invocation.setColorizedOutput(true); 114 | // invocation.setDisableHostKeyCheck(true); 115 | invocation.setUnbufferedOutput(true); 116 | // When 117 | invocation.execute(runner); 118 | // Then 119 | ArgumentCaptor> argument = ArgumentCaptor.forClass(Map.class); 120 | verify(runner).execute(any(ArgumentListBuilder.class), argument.capture()); 121 | 122 | assertThat(argument.getValue(), aMapWithSize(2)); 123 | assertThat(argument.getValue(), hasEntry("PYTHONUNBUFFERED", "1")); 124 | assertThat(argument.getValue(), hasEntry("ANSIBLE_FORCE_COLOR", "true")); 125 | } 126 | 127 | @Test 128 | void should_handle_variables() throws Exception { 129 | // Given 130 | Inventory inventory = new InventoryPath("/tmp/hosts"); 131 | BuildListener listener = mock(BuildListener.class); 132 | CLIRunner runner = mock(CLIRunner.class); 133 | AbstractBuild build = mock(AbstractBuild.class); 134 | EnvVars vars = new EnvVars(); 135 | vars.put("MODULE", "ping"); 136 | when(build.getEnvironment(any(TaskListener.class))).thenReturn(vars); 137 | AnsibleAdHocCommandInvocation invocation = 138 | new AnsibleAdHocCommandInvocation("/usr/local/bin/ansible", build, listener); 139 | invocation.setHostPattern("localhost"); 140 | invocation.setInventory(inventory); 141 | invocation.setModule("${MODULE}"); 142 | invocation.setForks(5); 143 | // When 144 | invocation.execute(runner); 145 | // Then 146 | ArgumentCaptor argument = ArgumentCaptor.forClass(ArgumentListBuilder.class); 147 | verify(runner).execute(argument.capture(), anyMap()); 148 | 149 | assertThat(argument.getValue().toString(), is("/usr/local/bin/ansible localhost -i /tmp/hosts -m ping -f 5")); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AnsibleVaultBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.jenkinsci.plugins.ansible; 15 | 16 | import com.cloudbees.plugins.credentials.CredentialsProvider; 17 | import com.cloudbees.plugins.credentials.common.StandardCredentials; 18 | import edu.umd.cs.findbugs.annotations.NonNull; 19 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 20 | import hudson.AbortException; 21 | import hudson.EnvVars; 22 | import hudson.Extension; 23 | import hudson.FilePath; 24 | import hudson.Launcher; 25 | import hudson.Util; 26 | import hudson.model.Computer; 27 | import hudson.model.Node; 28 | import hudson.model.Run; 29 | import hudson.model.TaskListener; 30 | import hudson.tasks.BuildStepMonitor; 31 | import hudson.tasks.Builder; 32 | import hudson.util.FormValidation; 33 | import java.io.File; 34 | import java.io.IOException; 35 | import jenkins.tasks.SimpleBuildStep; 36 | import org.apache.commons.lang3.StringUtils; 37 | import org.kohsuke.stapler.DataBoundConstructor; 38 | import org.kohsuke.stapler.DataBoundSetter; 39 | import org.kohsuke.stapler.QueryParameter; 40 | 41 | /** 42 | * A builder which wraps an Ansible vault invocation. 43 | * 44 | * @author Michael Cresswell 45 | */ 46 | public class AnsibleVaultBuilder extends Builder implements SimpleBuildStep { 47 | 48 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 49 | public String ansibleName = null; 50 | 51 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 52 | public String action = "encrypt_string"; 53 | 54 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 55 | public String vaultCredentialsId = null; 56 | 57 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 58 | public String newVaultCredentialsId = null; 59 | 60 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 61 | public String vaultTmpPath = null; 62 | 63 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 64 | public String content = null; 65 | 66 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 67 | public String input = null; 68 | 69 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 70 | public String output = null; 71 | 72 | @DataBoundConstructor 73 | public AnsibleVaultBuilder() {} 74 | 75 | @DataBoundSetter 76 | public void setAnsibleName(String ansibleName) { 77 | this.ansibleName = ansibleName; 78 | } 79 | 80 | @DataBoundSetter 81 | public void setAction(String action) { 82 | this.action = action; 83 | } 84 | 85 | @DataBoundSetter 86 | public void setVaultCredentialsId(String vaultCredentialsId) { 87 | this.vaultCredentialsId = vaultCredentialsId; 88 | } 89 | 90 | @DataBoundSetter 91 | public void setNewVaultCredentialsId(String newVaultCredentialsId) { 92 | this.newVaultCredentialsId = newVaultCredentialsId; 93 | } 94 | 95 | @DataBoundSetter 96 | public void setVaultTmpPath(String vaultTmpPath) { 97 | this.vaultTmpPath = vaultTmpPath; 98 | } 99 | 100 | @DataBoundSetter 101 | public void setContent(String content) { 102 | this.content = content; 103 | } 104 | 105 | @DataBoundSetter 106 | public void setInput(String input) { 107 | this.input = input; 108 | } 109 | 110 | @DataBoundSetter 111 | public void setOutput(String output) { 112 | this.output = output; 113 | } 114 | 115 | @Override 116 | public void perform( 117 | @NonNull Run run, @NonNull FilePath ws, @NonNull Launcher launcher, @NonNull TaskListener listener) 118 | throws InterruptedException, IOException { 119 | Computer computer = ws.toComputer(); 120 | Node node; 121 | if (computer == null || (node = computer.getNode()) == null) { 122 | throw new AbortException("The ansible vault build step requires to be launched on a node"); 123 | } 124 | perform(run, node, ws, launcher, listener, run.getEnvironment(listener)); 125 | } 126 | 127 | public void perform( 128 | @NonNull Run run, 129 | @NonNull Node node, 130 | @NonNull FilePath ws, 131 | @NonNull Launcher launcher, 132 | @NonNull TaskListener listener, 133 | EnvVars envVars) 134 | throws InterruptedException, IOException { 135 | try { 136 | CLIRunner runner = new CLIRunner(run, ws, launcher, listener); 137 | Computer computer = node.toComputer(); 138 | String exe = AnsibleInstallation.getExecutable( 139 | ansibleName, AnsibleCommand.ANSIBLE_VAULT, node, listener, envVars); 140 | AnsibleVaultInvocation invocation = new AnsibleVaultInvocation(exe, run, ws, listener, envVars); 141 | invocation.setAction(action); 142 | invocation.setVaultCredentials( 143 | StringUtils.isNotBlank(vaultCredentialsId) 144 | ? CredentialsProvider.findCredentialById( 145 | run.getEnvironment(listener).expand(vaultCredentialsId), 146 | StandardCredentials.class, 147 | run) 148 | : null); 149 | invocation.setNewVaultCredentials( 150 | StringUtils.isNotBlank(newVaultCredentialsId) 151 | ? CredentialsProvider.findCredentialById( 152 | run.getEnvironment(listener).expand(newVaultCredentialsId), 153 | StandardCredentials.class, 154 | run) 155 | : null); 156 | invocation.setVaultTmpPath( 157 | StringUtils.isNotBlank(vaultTmpPath) 158 | ? new FilePath(computer.getChannel(), new File(vaultTmpPath).getAbsolutePath()) 159 | : null); 160 | invocation.setContent(content); 161 | invocation.setInput(input); 162 | invocation.setOutput(output); 163 | if (!invocation.execute(runner)) { 164 | throw new AbortException("Ansible vault execution failed"); 165 | } 166 | } catch (IOException ioe) { 167 | Util.displayIOException(ioe, listener); 168 | ioe.printStackTrace(listener.fatalError("command execution failed")); 169 | throw ioe; 170 | } catch (AnsibleInvocationException aie) { 171 | listener.fatalError(aie.getMessage()); 172 | throw new AbortException(aie.getMessage()); 173 | } 174 | } 175 | 176 | @Override 177 | public BuildStepMonitor getRequiredMonitorService() { 178 | return BuildStepMonitor.NONE; 179 | } 180 | 181 | @Extension 182 | public static final class DescriptorImpl extends AbstractAnsibleBuilderDescriptor { 183 | public DescriptorImpl() { 184 | super("Invoke Ansible Vault"); 185 | } 186 | 187 | public FormValidation doCheckVaultCredentialsId(@QueryParameter String vaultCredentialsId) { 188 | return checkNotNullOrEmpty(vaultCredentialsId, "Vault credentials must not be empty"); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/jobdsl/context/AnsibleContext.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible.jobdsl.context; 2 | 3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 4 | import java.util.List; 5 | import javaposse.jobdsl.dsl.Context; 6 | import javaposse.jobdsl.plugin.ContextExtensionPoint; 7 | import org.jenkinsci.plugins.ansible.ExtraVar; 8 | import org.jenkinsci.plugins.ansible.Inventory; 9 | import org.jenkinsci.plugins.ansible.InventoryContent; 10 | import org.jenkinsci.plugins.ansible.InventoryPath; 11 | 12 | /** 13 | * @author lanwen (Merkushev Kirill) 14 | */ 15 | public class AnsibleContext implements Context { 16 | private Inventory inventory; 17 | private String ansibleName; 18 | private String action; 19 | private String credentialsId; 20 | private String vaultCredentialsId; 21 | private String newVaultCredentialsId; 22 | private String vaultTmpPath = null; 23 | private String content; 24 | private String input; 25 | private String output; 26 | private boolean become = false; 27 | private String becomeUser = "root"; 28 | private boolean sudo = false; 29 | private String sudoUser = "root"; 30 | private int forks = 5; 31 | private boolean checkMode = false; 32 | private boolean unbufferedOutput = true; 33 | private boolean colorizedOutput = false; 34 | private boolean disableHostKeyChecking = false; 35 | 36 | @Deprecated 37 | @SuppressWarnings("unused") 38 | @SuppressFBWarnings("URF_UNREAD_FIELD") 39 | private transient boolean hostKeyChecking = true; 40 | 41 | private String additionalParameters; 42 | ExtraVarsContext extraVarsContext = new ExtraVarsContext(); 43 | 44 | /* adhoc-only */ 45 | 46 | private String hostPattern; 47 | 48 | /* playbook-only */ 49 | 50 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 51 | public String limit; 52 | 53 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 54 | public String tags; 55 | 56 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 57 | public String skippedTags; 58 | 59 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 60 | public String startAtTask; 61 | 62 | public void inventoryContent(String content, boolean dynamic) { 63 | this.inventory = new InventoryContent(content, dynamic); 64 | } 65 | 66 | public void inventoryContent(String content) { 67 | this.inventory = new InventoryContent(content, false); 68 | } 69 | 70 | public void inventoryPath(String path) { 71 | this.inventory = new InventoryPath(path); 72 | } 73 | 74 | public void ansibleName(String ansibleName) { 75 | this.ansibleName = ansibleName; 76 | } 77 | 78 | public void action(String action) { 79 | this.action = action; 80 | } 81 | 82 | public void credentialsId(String credentialsId) { 83 | this.credentialsId = credentialsId; 84 | } 85 | 86 | public void vaultCredentialsId(String vaultCredentialsId) { 87 | this.vaultCredentialsId = vaultCredentialsId; 88 | } 89 | 90 | public void newVaultCredentialsId(String newVaultCredentialsId) { 91 | this.newVaultCredentialsId = newVaultCredentialsId; 92 | } 93 | 94 | public void setVaultTmpPath(String vaultTmpPath) { 95 | this.vaultTmpPath = vaultTmpPath; 96 | } 97 | 98 | public void content(String content) { 99 | this.content = content; 100 | } 101 | 102 | public void input(String input) { 103 | this.input = input; 104 | } 105 | 106 | public void output(String output) { 107 | this.output = output; 108 | } 109 | 110 | public void become(boolean become) { 111 | this.become = become; 112 | } 113 | 114 | public void becomeUser(String becomeUser) { 115 | this.becomeUser = becomeUser; 116 | } 117 | 118 | public void checkMode(boolean checkMode) { 119 | this.checkMode = checkMode; 120 | } 121 | 122 | public void sudo(boolean sudo) { 123 | this.sudo = sudo; 124 | } 125 | 126 | public void sudoUser(String sudoUser) { 127 | this.sudoUser = sudoUser; 128 | } 129 | 130 | public void forks(int forks) { 131 | this.forks = forks; 132 | } 133 | 134 | public void unbufferedOutput(boolean unbufferedOutput) { 135 | this.unbufferedOutput = unbufferedOutput; 136 | } 137 | 138 | public void colorizedOutput(boolean colorizedOutput) { 139 | this.colorizedOutput = colorizedOutput; 140 | } 141 | 142 | public void disableHostKeyChecking(boolean disableHostKeyChecking) { 143 | this.disableHostKeyChecking = disableHostKeyChecking; 144 | } 145 | 146 | public void additionalParameters(String additionalParameters) { 147 | this.additionalParameters = additionalParameters; 148 | } 149 | 150 | public void hostPattern(String hostPattern) { 151 | this.hostPattern = hostPattern; 152 | } 153 | 154 | public void limit(String limit) { 155 | this.limit = limit; 156 | } 157 | 158 | public void tags(String tags) { 159 | this.tags = tags; 160 | } 161 | 162 | public void skippedTags(String skippedTags) { 163 | this.skippedTags = skippedTags; 164 | } 165 | 166 | public void startAtTask(String startAtTask) { 167 | this.startAtTask = startAtTask; 168 | } 169 | 170 | public void extraVars(Runnable closure) { 171 | ContextExtensionPoint.executeInContext(closure, extraVarsContext); 172 | } 173 | 174 | public String getAction() { 175 | return action; 176 | } 177 | 178 | public String getAnsibleName() { 179 | return ansibleName; 180 | } 181 | 182 | public String getCredentialsId() { 183 | return credentialsId; 184 | } 185 | 186 | public String getVaultCredentialsId() { 187 | return vaultCredentialsId; 188 | } 189 | 190 | public String getNewVaultCredentialsId() { 191 | return newVaultCredentialsId; 192 | } 193 | 194 | public String getVaultTmpPath() { 195 | return vaultTmpPath; 196 | } 197 | 198 | public String getContent() { 199 | return content; 200 | } 201 | 202 | public String getInput() { 203 | return input; 204 | } 205 | 206 | public String getOutput() { 207 | return output; 208 | } 209 | 210 | public Inventory getInventory() { 211 | return inventory; 212 | } 213 | 214 | public boolean isBecome() { 215 | return become; 216 | } 217 | 218 | public String getBecomeUser() { 219 | return becomeUser; 220 | } 221 | 222 | public boolean isSudo() { 223 | return sudo; 224 | } 225 | 226 | public String getSudoUser() { 227 | return sudoUser; 228 | } 229 | 230 | public int getForks() { 231 | return forks; 232 | } 233 | 234 | public boolean isUnbufferedOutput() { 235 | return unbufferedOutput; 236 | } 237 | 238 | public boolean isColorizedOutput() { 239 | return colorizedOutput; 240 | } 241 | 242 | public boolean isCheckMode() { 243 | return checkMode; 244 | } 245 | 246 | public boolean isDisableHostKeyChecking() { 247 | return disableHostKeyChecking; 248 | } 249 | 250 | public String getAdditionalParameters() { 251 | return additionalParameters; 252 | } 253 | 254 | public String getHostPattern() { 255 | return hostPattern; 256 | } 257 | 258 | public String getLimit() { 259 | return limit; 260 | } 261 | 262 | public String getTags() { 263 | return tags; 264 | } 265 | 266 | public String getSkippedTags() { 267 | return skippedTags; 268 | } 269 | 270 | public String getStartAtTask() { 271 | return startAtTask; 272 | } 273 | 274 | public List getExtraVars() { 275 | return extraVarsContext.getExtraVars(); 276 | } 277 | 278 | @Deprecated 279 | public void hostKeyChecking(boolean hostKeyChecking) {} 280 | 281 | @Deprecated 282 | public boolean isHostKeyChecking() { 283 | return true; 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/workflow/AnsibleVaultStep.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 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.workflow; 17 | 18 | import static com.cloudbees.plugins.credentials.CredentialsMatchers.anyOf; 19 | import static com.cloudbees.plugins.credentials.CredentialsMatchers.instanceOf; 20 | 21 | import com.cloudbees.plugins.credentials.CredentialsProvider; 22 | import com.cloudbees.plugins.credentials.common.StandardCredentials; 23 | import com.cloudbees.plugins.credentials.common.StandardListBoxModel; 24 | import com.google.inject.Inject; 25 | import hudson.*; 26 | import hudson.model.Computer; 27 | import hudson.model.Item; 28 | import hudson.model.Node; 29 | import hudson.model.Run; 30 | import hudson.model.TaskListener; 31 | import hudson.util.ListBoxModel; 32 | import jenkins.model.Jenkins; 33 | import org.jenkinsci.plugins.ansible.AnsibleInstallation; 34 | import org.jenkinsci.plugins.ansible.AnsibleVaultBuilder; 35 | import org.jenkinsci.plugins.plaincredentials.FileCredentials; 36 | import org.jenkinsci.plugins.plaincredentials.StringCredentials; 37 | import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl; 38 | import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl; 39 | import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution; 40 | import org.jenkinsci.plugins.workflow.steps.StepContextParameter; 41 | import org.kohsuke.stapler.AncestorInPath; 42 | import org.kohsuke.stapler.DataBoundConstructor; 43 | import org.kohsuke.stapler.DataBoundSetter; 44 | import org.kohsuke.stapler.QueryParameter; 45 | 46 | /** 47 | * The Ansible vault invocation step for the Jenkins workflow plugin. 48 | */ 49 | public class AnsibleVaultStep extends AbstractStepImpl { 50 | 51 | private String installation; 52 | private String action; 53 | private String vaultCredentialsId; 54 | private String vaultTmpPath; 55 | private String newVaultCredentialsId; 56 | private String content = null; 57 | private String input = null; 58 | private String output = null; 59 | 60 | @DataBoundConstructor 61 | public AnsibleVaultStep() {} 62 | 63 | @DataBoundSetter 64 | public void setAction(String action) { 65 | this.action = action; 66 | } 67 | 68 | @DataBoundSetter 69 | public void setVaultCredentialsId(String vaultCredentialsId) { 70 | this.vaultCredentialsId = Util.fixEmptyAndTrim(vaultCredentialsId); 71 | } 72 | 73 | @DataBoundSetter 74 | public void setNewVaultCredentialsId(String newVaultCredentialsId) { 75 | this.newVaultCredentialsId = Util.fixEmptyAndTrim(newVaultCredentialsId); 76 | } 77 | 78 | @DataBoundSetter 79 | public void setVaultTmpPath(String vaultTmpPath) { 80 | this.vaultTmpPath = vaultTmpPath; 81 | } 82 | 83 | @DataBoundSetter 84 | public void setContent(String content) { 85 | this.content = content; 86 | } 87 | 88 | @DataBoundSetter 89 | public void setInput(String input) { 90 | this.input = input; 91 | } 92 | 93 | @DataBoundSetter 94 | public void setOutput(String output) { 95 | this.output = output; 96 | } 97 | 98 | @DataBoundSetter 99 | public void setInstallation(String installation) { 100 | this.installation = Util.fixEmptyAndTrim(installation); 101 | } 102 | 103 | public String getInstallation() { 104 | return installation; 105 | } 106 | 107 | public String getAction() { 108 | return action; 109 | } 110 | 111 | public String getVaultCredentialsId() { 112 | return vaultCredentialsId; 113 | } 114 | 115 | public String getNewVaultCredentialsId() { 116 | return newVaultCredentialsId; 117 | } 118 | 119 | public String getVaultTmpPath() { 120 | return vaultTmpPath; 121 | } 122 | 123 | public String getContent() { 124 | return content; 125 | } 126 | 127 | public String getInput() { 128 | return input; 129 | } 130 | 131 | public String getOutput() { 132 | return output; 133 | } 134 | 135 | @Extension 136 | public static final class DescriptorImpl extends AbstractStepDescriptorImpl { 137 | 138 | public DescriptorImpl() { 139 | super(AnsibleVaultExecution.class); 140 | } 141 | 142 | @Override 143 | public String getFunctionName() { 144 | return "ansibleVault"; 145 | } 146 | 147 | @Override 148 | public String getDisplayName() { 149 | return "Invoke ansible vault"; 150 | } 151 | 152 | public ListBoxModel doFillVaultCredentialsIdItems( 153 | @AncestorInPath Item item, @QueryParameter String vaultCredentialsId) { 154 | return fillVaultCredentials(item, vaultCredentialsId); 155 | } 156 | 157 | public ListBoxModel doFillNewVaultCredentialsIdItems( 158 | @AncestorInPath Item item, @QueryParameter String newVaultCredentialsId) { 159 | return fillVaultCredentials(item, newVaultCredentialsId); 160 | } 161 | 162 | public ListBoxModel doFillInstallationItems() { 163 | ListBoxModel model = new ListBoxModel(); 164 | for (AnsibleInstallation tool : AnsibleInstallation.allInstallations()) { 165 | model.add(tool.getName()); 166 | } 167 | return model; 168 | } 169 | 170 | private ListBoxModel fillVaultCredentials(Item item, String credentialsId) { 171 | StandardListBoxModel result = new StandardListBoxModel(); 172 | if (item == null) { 173 | if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) { 174 | return result.includeCurrentValue(credentialsId); 175 | } 176 | } else { 177 | if (!item.hasPermission(Item.EXTENDED_READ) && !item.hasPermission(CredentialsProvider.USE_ITEM)) { 178 | return result.includeCurrentValue(credentialsId); 179 | } 180 | } 181 | 182 | return result.includeEmptyValue() 183 | .withMatching( 184 | anyOf(instanceOf(FileCredentials.class), instanceOf(StringCredentials.class)), 185 | CredentialsProvider.lookupCredentials(StandardCredentials.class, item)) 186 | .includeCurrentValue(credentialsId); 187 | } 188 | } 189 | 190 | public static final class AnsibleVaultExecution extends AbstractSynchronousNonBlockingStepExecution { 191 | 192 | private static final long serialVersionUID = 1; 193 | 194 | @Inject 195 | private transient AnsibleVaultStep step; 196 | 197 | @StepContextParameter 198 | private transient TaskListener listener; 199 | 200 | @StepContextParameter 201 | private transient Launcher launcher; 202 | 203 | @StepContextParameter 204 | private transient Run run; 205 | 206 | @StepContextParameter 207 | private transient FilePath ws; 208 | 209 | @StepContextParameter 210 | private transient EnvVars envVars; 211 | 212 | @StepContextParameter 213 | private transient Computer computer; 214 | 215 | @Override 216 | protected Void run() throws Exception { 217 | AnsibleVaultBuilder builder = new AnsibleVaultBuilder(); 218 | builder.setAnsibleName(step.getInstallation()); 219 | builder.setAction(step.getAction()); 220 | builder.setVaultCredentialsId(step.getVaultCredentialsId()); 221 | builder.setNewVaultCredentialsId(step.getNewVaultCredentialsId()); 222 | builder.setVaultTmpPath(step.getVaultTmpPath()); 223 | builder.setContent(step.getContent()); 224 | builder.setInput(step.getInput()); 225 | builder.setOutput(step.getOutput()); 226 | Node node; 227 | if (computer == null || (node = computer.getNode()) == null) { 228 | throw new AbortException("The ansible vault build step requires to be launched on a node"); 229 | } 230 | builder.perform(run, node, ws, launcher, listener, envVars); 231 | return null; 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/workflow/AnsibleAdhocStep.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 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.workflow; 17 | 18 | import static com.cloudbees.plugins.credentials.CredentialsMatchers.anyOf; 19 | import static com.cloudbees.plugins.credentials.CredentialsMatchers.instanceOf; 20 | 21 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 22 | import com.cloudbees.plugins.credentials.CredentialsProvider; 23 | import com.cloudbees.plugins.credentials.common.StandardCredentials; 24 | import com.cloudbees.plugins.credentials.common.StandardListBoxModel; 25 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 26 | import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; 27 | import com.google.inject.Inject; 28 | import hudson.*; 29 | import hudson.model.Computer; 30 | import hudson.model.Project; 31 | import hudson.model.Run; 32 | import hudson.model.TaskListener; 33 | import hudson.util.ListBoxModel; 34 | import java.util.List; 35 | import org.apache.commons.lang3.StringUtils; 36 | import org.jenkinsci.plugins.ansible.AnsibleAdHocCommandBuilder; 37 | import org.jenkinsci.plugins.ansible.AnsibleInstallation; 38 | import org.jenkinsci.plugins.ansible.ExtraVar; 39 | import org.jenkinsci.plugins.ansible.Inventory; 40 | import org.jenkinsci.plugins.ansible.InventoryContent; 41 | import org.jenkinsci.plugins.ansible.InventoryDoNotSpecify; 42 | import org.jenkinsci.plugins.ansible.InventoryPath; 43 | import org.jenkinsci.plugins.plaincredentials.FileCredentials; 44 | import org.jenkinsci.plugins.plaincredentials.StringCredentials; 45 | import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl; 46 | import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl; 47 | import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution; 48 | import org.jenkinsci.plugins.workflow.steps.StepContextParameter; 49 | import org.kohsuke.stapler.AncestorInPath; 50 | import org.kohsuke.stapler.DataBoundConstructor; 51 | import org.kohsuke.stapler.DataBoundSetter; 52 | 53 | /** 54 | * The Ansible adhoc invocation step for the Jenkins workflow plugin. 55 | */ 56 | public class AnsibleAdhocStep extends AbstractStepImpl { 57 | 58 | private String hosts; 59 | private String module; 60 | private String moduleArguments; 61 | private String inventory; 62 | private String inventoryContent; 63 | private boolean dynamicInventory = false; 64 | private String installation; 65 | private String credentialsId; 66 | private String vaultCredentialsId; 67 | private String vaultTmpPath = null; 68 | private boolean become = false; 69 | private String becomeUser = "root"; 70 | private List extraVars = null; 71 | private String extras = null; 72 | private boolean colorized = false; 73 | private int forks = 0; 74 | private boolean hostKeyChecking = false; 75 | 76 | @DataBoundConstructor 77 | public AnsibleAdhocStep(String hosts) { 78 | this.hosts = hosts; 79 | } 80 | 81 | @DataBoundSetter 82 | public void setModule(String module) { 83 | this.module = Util.fixEmptyAndTrim(module); 84 | } 85 | 86 | @DataBoundSetter 87 | public void setModuleArguments(String moduleArguments) { 88 | this.moduleArguments = Util.fixEmptyAndTrim(moduleArguments); 89 | } 90 | 91 | @DataBoundSetter 92 | public void setInventory(String inventory) { 93 | this.inventory = Util.fixEmptyAndTrim(inventory); 94 | } 95 | 96 | @DataBoundSetter 97 | public void setInventoryContent(String inventoryContent) { 98 | this.inventoryContent = Util.fixEmptyAndTrim(inventoryContent); 99 | } 100 | 101 | @DataBoundSetter 102 | public void setDynamicInventory(boolean dynamicInventory) { 103 | this.dynamicInventory = dynamicInventory; 104 | } 105 | 106 | @DataBoundSetter 107 | public void setCredentialsId(String credentialsId) { 108 | this.credentialsId = Util.fixEmptyAndTrim(credentialsId); 109 | } 110 | 111 | @DataBoundSetter 112 | public void setVaultCredentialsId(String vaultCredentialsId) { 113 | this.vaultCredentialsId = Util.fixEmptyAndTrim(vaultCredentialsId); 114 | } 115 | 116 | @DataBoundSetter 117 | public void setVaultTmpPath(String vaultTmpPath) { 118 | this.vaultTmpPath = vaultTmpPath; 119 | } 120 | 121 | @DataBoundSetter 122 | public void setBecome(boolean become) { 123 | this.become = become; 124 | } 125 | 126 | @DataBoundSetter 127 | public void setBecomeUser(String becomeUser) { 128 | this.becomeUser = Util.fixEmptyAndTrim(becomeUser); 129 | } 130 | 131 | @DataBoundSetter 132 | public void setInstallation(String installation) { 133 | this.installation = Util.fixEmptyAndTrim(installation); 134 | } 135 | 136 | @DataBoundSetter 137 | public void setExtraVars(List extraVars) { 138 | this.extraVars = extraVars; 139 | } 140 | 141 | @DataBoundSetter 142 | public void setExtras(String extras) { 143 | this.extras = Util.fixEmptyAndTrim(extras); 144 | } 145 | 146 | @DataBoundSetter 147 | public void setColorized(boolean colorized) { 148 | this.colorized = colorized; 149 | } 150 | 151 | @DataBoundSetter 152 | public void setForks(int forks) { 153 | this.forks = forks; 154 | } 155 | 156 | @DataBoundSetter 157 | public void setHostKeyChecking(boolean hostKeyChecking) { 158 | this.hostKeyChecking = hostKeyChecking; 159 | } 160 | 161 | public String getInstallation() { 162 | return installation; 163 | } 164 | 165 | public String getHosts() { 166 | return hosts; 167 | } 168 | 169 | public String getModule() { 170 | return module; 171 | } 172 | 173 | public String getModuleArguments() { 174 | return moduleArguments; 175 | } 176 | 177 | public String getInventory() { 178 | return inventory; 179 | } 180 | 181 | public String getInventoryContent() { 182 | return inventoryContent; 183 | } 184 | 185 | public boolean isDynamicInventory() { 186 | return dynamicInventory; 187 | } 188 | 189 | public String getCredentialsId() { 190 | return credentialsId; 191 | } 192 | 193 | public String getVaultCredentialsId() { 194 | return vaultCredentialsId; 195 | } 196 | 197 | public String getVaultTmpPath() { 198 | return vaultTmpPath; 199 | } 200 | 201 | public boolean isBecome() { 202 | return become; 203 | } 204 | 205 | public String getBecomeUser() { 206 | return becomeUser; 207 | } 208 | 209 | public List getExtraVars() { 210 | return extraVars; 211 | } 212 | 213 | public String getExtras() { 214 | return extras; 215 | } 216 | 217 | public boolean isHostKeyChecking() { 218 | return hostKeyChecking; 219 | } 220 | 221 | public int getForks() { 222 | return forks; 223 | } 224 | 225 | public boolean isColorized() { 226 | return colorized; 227 | } 228 | 229 | @Extension 230 | public static final class DescriptorImpl extends AbstractStepDescriptorImpl { 231 | 232 | public DescriptorImpl() { 233 | super(AnsibleAdhocExecution.class); 234 | } 235 | 236 | @Override 237 | public String getFunctionName() { 238 | return "ansibleAdhoc"; 239 | } 240 | 241 | @Override 242 | public String getDisplayName() { 243 | return "Invoke an ansible adhoc command"; 244 | } 245 | 246 | public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Project project) { 247 | return new StandardListBoxModel() 248 | .withEmptySelection() 249 | .withMatching( 250 | anyOf(instanceOf(SSHUserPrivateKey.class), instanceOf(UsernamePasswordCredentials.class)), 251 | CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, project)); 252 | } 253 | 254 | public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Project project) { 255 | return new StandardListBoxModel() 256 | .withEmptySelection() 257 | .withMatching( 258 | anyOf(instanceOf(FileCredentials.class), instanceOf(StringCredentials.class)), 259 | CredentialsProvider.lookupCredentials(StandardCredentials.class, project)); 260 | } 261 | 262 | public ListBoxModel doFillInstallationItems() { 263 | ListBoxModel model = new ListBoxModel(); 264 | for (AnsibleInstallation tool : AnsibleInstallation.allInstallations()) { 265 | model.add(tool.getName()); 266 | } 267 | return model; 268 | } 269 | } 270 | 271 | public static final class AnsibleAdhocExecution extends AbstractSynchronousNonBlockingStepExecution { 272 | 273 | private static final long serialVersionUID = 1; 274 | 275 | @Inject 276 | private transient AnsibleAdhocStep step; 277 | 278 | @StepContextParameter 279 | private transient TaskListener listener; 280 | 281 | @StepContextParameter 282 | private transient Launcher launcher; 283 | 284 | @StepContextParameter 285 | private transient Run run; 286 | 287 | @StepContextParameter 288 | private transient FilePath ws; 289 | 290 | @StepContextParameter 291 | private transient EnvVars envVars; 292 | 293 | @StepContextParameter 294 | private transient Computer computer; 295 | 296 | @Override 297 | protected Void run() throws Exception { 298 | Inventory inventory = null; 299 | if (StringUtils.isNotBlank(step.getInventory())) { 300 | inventory = new InventoryPath(step.getInventory()); 301 | } else if (StringUtils.isNotBlank(step.getInventoryContent())) { 302 | inventory = new InventoryContent(step.getInventoryContent(), step.isDynamicInventory()); 303 | } else { 304 | inventory = new InventoryDoNotSpecify(); 305 | } 306 | AnsibleAdHocCommandBuilder builder = new AnsibleAdHocCommandBuilder( 307 | step.getHosts(), inventory, step.getModule(), step.getModuleArguments()); 308 | builder.setAnsibleName(step.getInstallation()); 309 | builder.setBecome(step.isBecome()); 310 | builder.setBecomeUser(step.getBecomeUser()); 311 | builder.setCredentialsId(step.getCredentialsId()); 312 | builder.setVaultCredentialsId(step.getVaultCredentialsId()); 313 | builder.setVaultTmpPath(step.getVaultTmpPath()); 314 | builder.setForks(step.getForks()); 315 | builder.setExtraVars(step.getExtraVars()); 316 | builder.setAdditionalParameters(step.getExtras()); 317 | builder.setHostKeyChecking(step.isHostKeyChecking()); 318 | builder.setColorizedOutput(step.isColorized()); 319 | builder.perform(run, ws, launcher, listener); 320 | return null; 321 | } 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /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 com.cloudbees.plugins.credentials.CredentialsProvider; 19 | import com.cloudbees.plugins.credentials.common.StandardCredentials; 20 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 21 | import edu.umd.cs.findbugs.annotations.NonNull; 22 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 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.Computer; 30 | import hudson.model.Run; 31 | import hudson.model.TaskListener; 32 | import hudson.tasks.Builder; 33 | import hudson.util.FormValidation; 34 | import java.io.File; 35 | import java.io.IOException; 36 | import java.util.List; 37 | import jenkins.tasks.SimpleBuildStep; 38 | import org.apache.commons.lang3.StringUtils; 39 | import org.kohsuke.stapler.DataBoundConstructor; 40 | import org.kohsuke.stapler.DataBoundSetter; 41 | import org.kohsuke.stapler.QueryParameter; 42 | 43 | /** 44 | * A builder which wraps an Ansible Ad-Hoc command invocation. 45 | */ 46 | public class AnsibleAdHocCommandBuilder extends Builder implements SimpleBuildStep { 47 | 48 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 49 | public String ansibleName; 50 | 51 | // SSH settings 52 | /** 53 | * The id of the credentials to use. 54 | */ 55 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 56 | public String credentialsId = null; 57 | 58 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 59 | public String vaultCredentialsId = null; 60 | 61 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 62 | public String vaultTmpPath = null; 63 | 64 | public final String hostPattern; 65 | 66 | /** 67 | * Path to the inventory file. 68 | */ 69 | public final Inventory inventory; 70 | 71 | public final String module; 72 | 73 | public final String command; 74 | 75 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 76 | public boolean become = false; 77 | 78 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 79 | public String becomeUser = "root"; 80 | 81 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 82 | public boolean sudo = false; 83 | 84 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 85 | public String sudoUser = "root"; 86 | 87 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 88 | public int forks = 0; 89 | 90 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 91 | public boolean unbufferedOutput = true; 92 | 93 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 94 | public boolean colorizedOutput = false; 95 | 96 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 97 | public boolean disableHostKeyChecking = false; 98 | 99 | @Deprecated 100 | @SuppressWarnings("unused") 101 | @SuppressFBWarnings(value = {"URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", "PA_PUBLIC_PRIMITIVE_ATTRIBUTE"}) 102 | public transient boolean hostKeyChecking = true; 103 | 104 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 105 | public String additionalParameters = null; 106 | 107 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 108 | public List extraVars; 109 | 110 | @Deprecated 111 | public AnsibleAdHocCommandBuilder( 112 | String ansibleName, 113 | String hostPattern, 114 | Inventory inventory, 115 | String module, 116 | String command, 117 | String credentialsId, 118 | boolean sudo, 119 | String sudoUser, 120 | int forks, 121 | boolean unbufferedOutput, 122 | boolean colorizedOutput, 123 | boolean hostKeyChecking, 124 | String additionalParameters) { 125 | this.ansibleName = ansibleName; 126 | this.hostPattern = hostPattern; 127 | this.inventory = inventory; 128 | this.module = module; 129 | this.command = command; 130 | this.credentialsId = credentialsId; 131 | this.sudo = sudo; 132 | this.sudoUser = sudoUser; 133 | this.forks = forks; 134 | this.unbufferedOutput = unbufferedOutput; 135 | this.colorizedOutput = colorizedOutput; 136 | // ignored because of SECURITY-630 137 | // this.hostKeyChecking = hostKeyChecking; 138 | this.additionalParameters = additionalParameters; 139 | } 140 | 141 | @DataBoundConstructor 142 | public AnsibleAdHocCommandBuilder(String hostPattern, Inventory inventory, String module, String command) { 143 | this.hostPattern = hostPattern; 144 | this.inventory = inventory; 145 | this.module = module; 146 | this.command = command; 147 | } 148 | 149 | @DataBoundSetter 150 | public void setAnsibleName(String ansibleName) { 151 | this.ansibleName = ansibleName; 152 | } 153 | 154 | @DataBoundSetter 155 | public void setCredentialsId(String credentialsId) { 156 | this.credentialsId = credentialsId; 157 | } 158 | 159 | @DataBoundSetter 160 | public void setVaultCredentialsId(String vaultCredentialsId) { 161 | this.vaultCredentialsId = vaultCredentialsId; 162 | } 163 | 164 | @DataBoundSetter 165 | public void setVaultTmpPath(String vaultTmpPath) { 166 | this.vaultTmpPath = vaultTmpPath; 167 | } 168 | 169 | public void setBecome(boolean become) { 170 | this.become = become; 171 | } 172 | 173 | @DataBoundSetter 174 | public void setBecomeUser(String becomeUser) { 175 | this.becomeUser = becomeUser; 176 | } 177 | 178 | @DataBoundSetter 179 | public void setSudo(boolean sudo) { 180 | this.sudo = sudo; 181 | } 182 | 183 | @DataBoundSetter 184 | public void setSudoUser(String sudoUser) { 185 | this.sudoUser = sudoUser; 186 | } 187 | 188 | @DataBoundSetter 189 | public void setForks(int forks) { 190 | this.forks = forks; 191 | } 192 | 193 | @DataBoundSetter 194 | public void setUnbufferedOutput(boolean unbufferedOutput) { 195 | this.unbufferedOutput = unbufferedOutput; 196 | } 197 | 198 | @DataBoundSetter 199 | public void setColorizedOutput(boolean colorizedOutput) { 200 | this.colorizedOutput = colorizedOutput; 201 | } 202 | 203 | @DataBoundSetter 204 | public void setDisableHostKeyChecking(boolean disableHostKeyChecking) { 205 | this.disableHostKeyChecking = disableHostKeyChecking; 206 | } 207 | 208 | @DataBoundSetter 209 | @Deprecated 210 | public void setHostKeyChecking(boolean hostKeyChecking) { 211 | this.hostKeyChecking = true; 212 | } 213 | 214 | @DataBoundSetter 215 | public void setAdditionalParameters(String additionalParameters) { 216 | this.additionalParameters = additionalParameters; 217 | } 218 | 219 | @DataBoundSetter 220 | public void setExtraVars(List extraVars) { 221 | this.extraVars = extraVars; 222 | } 223 | 224 | @Override 225 | public void perform( 226 | @NonNull Run run, @NonNull FilePath ws, @NonNull Launcher launcher, @NonNull TaskListener listener) 227 | throws InterruptedException, IOException { 228 | try { 229 | CLIRunner runner = new CLIRunner(run, ws, launcher, listener); 230 | Computer computer = ws.toComputer(); 231 | if (computer == null) { 232 | throw new AbortException("The ansible ad-hoc command build step requires to be launched on a node"); 233 | } 234 | EnvVars envVars = run.getEnvironment(listener); 235 | String exe = AnsibleInstallation.getExecutable( 236 | ansibleName, AnsibleCommand.ANSIBLE, computer.getNode(), listener, envVars); 237 | AnsibleAdHocCommandInvocation invocation = 238 | new AnsibleAdHocCommandInvocation(exe, run, ws, listener, envVars); 239 | invocation.setHostPattern(hostPattern); 240 | invocation.setInventory(inventory); 241 | invocation.setModule(module); 242 | invocation.setModuleCommand(command); 243 | invocation.setBecome(become, becomeUser); 244 | invocation.setSudo(sudo, sudoUser); 245 | invocation.setForks(forks); 246 | invocation.setCredentials( 247 | StringUtils.isNotBlank(credentialsId) 248 | ? CredentialsProvider.findCredentialById( 249 | credentialsId, StandardUsernameCredentials.class, run) 250 | : null); 251 | invocation.setVaultCredentials( 252 | StringUtils.isNotBlank(vaultCredentialsId) 253 | ? CredentialsProvider.findCredentialById(vaultCredentialsId, StandardCredentials.class, run) 254 | : null); 255 | invocation.setVaultTmpPath( 256 | StringUtils.isNotBlank(vaultTmpPath) 257 | ? new FilePath(computer.getChannel(), new File(vaultTmpPath).getAbsolutePath()) 258 | : null); 259 | invocation.setExtraVars(extraVars); 260 | invocation.setAdditionalParameters(additionalParameters); 261 | invocation.setDisableHostKeyCheck(disableHostKeyChecking); 262 | invocation.setUnbufferedOutput(unbufferedOutput); 263 | invocation.setColorizedOutput(colorizedOutput); 264 | if (!invocation.execute(runner)) { 265 | throw new AbortException("Ansible Ad-Hoc command execution failed"); 266 | } 267 | } catch (IOException ioe) { 268 | Util.displayIOException(ioe, listener); 269 | ioe.printStackTrace(listener.fatalError("command execution failed")); 270 | throw ioe; 271 | } catch (AnsibleInvocationException aie) { 272 | listener.fatalError(aie.getMessage()); 273 | throw new AbortException(aie.getMessage()); 274 | } 275 | } 276 | 277 | @Extension 278 | public static final class DescriptorImpl extends AbstractAnsibleBuilderDescriptor { 279 | 280 | public DescriptorImpl() { 281 | super("Invoke Ansible Ad-Hoc Command"); 282 | } 283 | 284 | public FormValidation doCheckHostPattern(@QueryParameter String hostPattern) { 285 | return checkNotNullOrEmpty(hostPattern, "Host pattern must not be empty"); 286 | } 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ansible/jobdsl/JobDslIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ansible.jobdsl; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.allOf; 5 | import static org.hamcrest.Matchers.containsString; 6 | import static org.hamcrest.Matchers.hasItem; 7 | import static org.hamcrest.Matchers.is; 8 | import static org.hamcrest.Matchers.isA; 9 | import static org.hamcrest.Matchers.notNullValue; 10 | import static org.junit.jupiter.api.Assumptions.assumeFalse; 11 | 12 | import com.cloudbees.plugins.credentials.CredentialsProvider; 13 | import com.cloudbees.plugins.credentials.CredentialsScope; 14 | import com.cloudbees.plugins.credentials.CredentialsStore; 15 | import com.cloudbees.plugins.credentials.domains.Domain; 16 | import com.google.common.io.Resources; 17 | import hudson.model.FreeStyleBuild; 18 | import hudson.model.FreeStyleProject; 19 | import hudson.model.ParameterValue; 20 | import hudson.model.ParametersAction; 21 | import hudson.model.StringParameterValue; 22 | import hudson.util.Secret; 23 | import java.nio.charset.StandardCharsets; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | import javaposse.jobdsl.plugin.ExecuteDslScripts; 27 | import javaposse.jobdsl.plugin.LookupStrategy; 28 | import javaposse.jobdsl.plugin.RemovedJobAction; 29 | import javaposse.jobdsl.plugin.RemovedViewAction; 30 | import org.apache.commons.lang3.SystemUtils; 31 | import org.jenkinsci.plugins.ansible.AnsibleAdHocCommandBuilder; 32 | import org.jenkinsci.plugins.ansible.AnsiblePlaybookBuilder; 33 | import org.jenkinsci.plugins.ansible.AnsibleVaultBuilder; 34 | import org.jenkinsci.plugins.ansible.InventoryContent; 35 | import org.jenkinsci.plugins.ansible.InventoryPath; 36 | import org.jenkinsci.plugins.plaincredentials.StringCredentials; 37 | import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl; 38 | import org.junit.jupiter.api.Test; 39 | import org.jvnet.hudson.test.JenkinsRule; 40 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 41 | 42 | /** 43 | * @author lanwen (Merkushev Kirill) 44 | */ 45 | @WithJenkins 46 | class JobDslIntegrationTest { 47 | private static final String JOB_NAME_IN_DSL_SCRIPT = "ansible"; 48 | 49 | private static final String ANSIBLE_DSL_GROOVY_PLAYBOOK = "jobdsl/playbook.groovy"; 50 | private static final String ANSIBLE_DSL_GROOVY_EXPANDER = "jobdsl/expander.groovy"; 51 | private static final String ANSIBLE_DSL_GROOVY_CHECK_MODE = "jobdsl/checkMode.groovy"; 52 | private static final String ANSIBLE_DSL_GROOVY_SECURITY_630 = "jobdsl/security630.groovy"; 53 | private static final String ANSIBLE_DSL_GROOVY_PLAYBOOK_LEGACY = "jobdsl/legacyPlaybook.groovy"; 54 | private static final String ANSIBLE_DSL_GROOVY_ADHOC = "jobdsl/adhoc.groovy"; 55 | private static final String ANSIBLE_DSL_GROOVY_VAULT = "jobdsl/vault.groovy"; 56 | private static final String ANSIBLE_DSL_GROOVY_PLAYBOOK_BUILDER = "jobdsl/playbookBuilder.groovy"; 57 | 58 | @Test 59 | void shouldCreateJobSecurity630Dsl(JenkinsRule r) throws Exception { 60 | AnsiblePlaybookBuilder step = generateJob(r, ANSIBLE_DSL_GROOVY_SECURITY_630) 61 | .getBuildersList() 62 | .get(AnsiblePlaybookBuilder.class); 63 | assertThat("Should add playbook builder", step, notNullValue()); 64 | assertThat("disableHostKeyChecking", step.disableHostKeyChecking, is(false)); 65 | } 66 | 67 | @Test 68 | void shouldCreateJobWithPlaybookDsl(JenkinsRule r) throws Exception { 69 | AnsiblePlaybookBuilder step = 70 | generateJob(r, ANSIBLE_DSL_GROOVY_PLAYBOOK).getBuildersList().get(AnsiblePlaybookBuilder.class); 71 | assertThat("Should add playbook builder", step, notNullValue()); 72 | 73 | assertThat("playbook", step.playbook, is("path/playbook.yml")); 74 | assertThat("inventory", step.inventory, isA(InventoryPath.class)); 75 | assertThat("ansibleName", step.ansibleName, is("1.9.4")); 76 | assertThat("limit", step.limit, is("retry.limit")); 77 | assertThat("tags", step.tags, is("one,two")); 78 | assertThat("skippedTags", step.skippedTags, is("three")); 79 | assertThat("startAtTask", step.startAtTask, is("task")); 80 | assertThat("credentialsId", step.credentialsId, is("credsid")); 81 | assertThat("become", step.become, is(true)); 82 | assertThat("becomeUser", step.becomeUser, is("user")); 83 | assertThat("checkMode", step.checkMode, is(false)); 84 | assertThat("sudo", step.sudo, is(false)); 85 | assertThat("sudoUser", step.sudoUser, is("root")); 86 | assertThat("forks", step.forks, is(6)); 87 | assertThat("unbufferedOutput", step.unbufferedOutput, is(false)); 88 | assertThat("colorizedOutput", step.colorizedOutput, is(true)); 89 | assertThat("disableHostKeyChecking", step.disableHostKeyChecking, is(false)); 90 | assertThat("additionalParameters", step.additionalParameters, is("params")); 91 | assertThat("extraVar.key", step.extraVars.get(0).getKey(), is("key")); 92 | assertThat("extraVar.value", step.extraVars.get(0).getSecretValue().getPlainText(), is("value")); 93 | assertThat("extraVar.hidden", step.extraVars.get(0).isHidden(), is(true)); 94 | } 95 | 96 | @Test 97 | void shouldCreateJobWithCheckMode(JenkinsRule r) throws Exception { 98 | AnsiblePlaybookBuilder step = 99 | generateJob(r, ANSIBLE_DSL_GROOVY_CHECK_MODE).getBuildersList().get(AnsiblePlaybookBuilder.class); 100 | assertThat("Should add playbook builder", step, notNullValue()); 101 | 102 | assertThat("playbook", step.playbook, is("path/playbook.yml")); 103 | assertThat("inventory", step.inventory, isA(InventoryPath.class)); 104 | assertThat("ansibleName", step.ansibleName, is("1.9.4")); 105 | assertThat("checkMode", step.checkMode, is(true)); 106 | } 107 | 108 | @Test 109 | void shouldCreateJobWithVarExpander(JenkinsRule r) throws Exception { 110 | 111 | assumeFalse(SystemUtils.IS_OS_WINDOWS); 112 | 113 | // Add credentials 114 | StringCredentials vaultCredentials = new StringCredentialsImpl( 115 | CredentialsScope.GLOBAL, 116 | "vaultCredentialsString", 117 | "test username password", 118 | Secret.fromString("test-secret")); 119 | StringCredentials credentials = new StringCredentialsImpl( 120 | CredentialsScope.GLOBAL, "credentialsString", "test credentials", Secret.fromString("test")); 121 | CredentialsStore store = 122 | CredentialsProvider.lookupStores(r.jenkins).iterator().next(); 123 | store.addCredentials(Domain.global(), vaultCredentials); 124 | store.addCredentials(Domain.global(), credentials); 125 | 126 | // Create job via jobdsl with var expander 127 | AnsiblePlaybookBuilder step = 128 | generateJob(r, ANSIBLE_DSL_GROOVY_EXPANDER).getBuildersList().get(AnsiblePlaybookBuilder.class); 129 | assertThat("Should add playbook builder", step, notNullValue()); 130 | assertThat("playbook", step.playbook, is("playbook.yml")); 131 | assertThat("inventory", step.inventory, isA(InventoryPath.class)); 132 | assertThat("vaultCredentialsId", step.vaultCredentialsId, is("${vault_credentials_id}")); 133 | assertThat("credentialsId", step.credentialsId, is("${credentials_id}")); 134 | 135 | List parameters = new ArrayList<>(); 136 | parameters.add(new StringParameterValue("inventory_repository", "/ansible")); 137 | parameters.add(new StringParameterValue("vault_credentials_id", "vaultCredentialsString")); 138 | parameters.add(new StringParameterValue("credentials_id", "credentialsString")); 139 | ParametersAction parametersAction = new ParametersAction(parameters); 140 | 141 | FreeStyleProject freeStyleProject = r.getInstance().getItemByFullName("ansible", FreeStyleProject.class); 142 | FreeStyleBuild build = 143 | freeStyleProject.scheduleBuild2(0, parametersAction).get(); 144 | assertThat( 145 | build.getLog(), 146 | allOf(containsString( 147 | "ansible-playbook playbook.yml -i /ansible/inventory.yml -f 5 --vault-password-file "))); 148 | } 149 | 150 | @Test 151 | void shouldCreateJobWithLegacyPlaybookDsl(JenkinsRule r) throws Exception { 152 | AnsiblePlaybookBuilder step = generateJob(r, ANSIBLE_DSL_GROOVY_PLAYBOOK_LEGACY) 153 | .getBuildersList() 154 | .get(AnsiblePlaybookBuilder.class); 155 | assertThat("Should add playbook builder", step, notNullValue()); 156 | 157 | assertThat("playbook", step.playbook, is("path/playbook.yml")); 158 | assertThat("inventory", step.inventory, isA(InventoryPath.class)); 159 | assertThat("ansibleName", step.ansibleName, is("1.9.4")); 160 | assertThat("limit", step.limit, is("retry.limit")); 161 | assertThat("tags", step.tags, is("one,two")); 162 | assertThat("skippedTags", step.skippedTags, is("three")); 163 | assertThat("startAtTask", step.startAtTask, is("task")); 164 | assertThat("credentialsId", step.credentialsId, is("credsid")); 165 | assertThat("become", step.become, is(false)); 166 | assertThat("becomeUser", step.becomeUser, is("root")); 167 | assertThat("sudo", step.sudo, is(true)); 168 | assertThat("sudoUser", step.sudoUser, is("user")); 169 | assertThat("forks", step.forks, is(6)); 170 | assertThat("unbufferedOutput", step.unbufferedOutput, is(false)); 171 | assertThat("colorizedOutput", step.colorizedOutput, is(true)); 172 | assertThat("disableHostKeyChecking", step.disableHostKeyChecking, is(true)); 173 | assertThat("additionalParameters", step.additionalParameters, is("params")); 174 | assertThat("extraVar.key", step.extraVars.get(0).getKey(), is("key")); 175 | assertThat("extraVar.value", step.extraVars.get(0).getSecretValue().getPlainText(), is("value")); 176 | assertThat("extraVar.hidden", step.extraVars.get(0).isHidden(), is(true)); 177 | } 178 | 179 | @Test 180 | void shouldCreateJobAdhocDsl(JenkinsRule r) throws Exception { 181 | AnsibleAdHocCommandBuilder step = 182 | generateJob(r, ANSIBLE_DSL_GROOVY_ADHOC).getBuildersList().get(AnsibleAdHocCommandBuilder.class); 183 | assertThat("Should add adhoc builder", step, notNullValue()); 184 | 185 | assertThat("module", step.module, is("module")); 186 | assertThat("inventory", step.inventory, isA(InventoryContent.class)); 187 | assertThat("ansibleName", step.ansibleName, is("1.9.1")); 188 | 189 | assertThat("credentialsId", step.credentialsId, is("credsid")); 190 | assertThat("hostPattern", step.hostPattern, is("pattern")); 191 | assertThat("become", step.become, is(false)); 192 | assertThat("becomeUser", step.becomeUser, is("root")); 193 | assertThat("forks", step.forks, is(5)); 194 | assertThat("unbufferedOutput", step.unbufferedOutput, is(true)); 195 | assertThat("colorizedOutput", step.colorizedOutput, is(false)); 196 | assertThat("disableHostKeyChecking", step.disableHostKeyChecking, is(false)); 197 | } 198 | 199 | @Test 200 | void shouldCreateJobWithVaultDsl(JenkinsRule r) throws Exception { 201 | AnsibleVaultBuilder step = 202 | generateJob(r, ANSIBLE_DSL_GROOVY_VAULT).getBuildersList().get(AnsibleVaultBuilder.class); 203 | assertThat("Should add playbook builder", step, notNullValue()); 204 | 205 | assertThat("action", step.action, is("encrypt_string")); 206 | assertThat("content", step.content, is("my_secret")); 207 | assertThat("vaultCredentialsId", step.vaultCredentialsId, is("ansible_vault_credentials")); 208 | } 209 | 210 | @Test 211 | void shouldCreateJobWithPlaybookBuilderDsl(JenkinsRule r) throws Exception { 212 | AnsiblePlaybookBuilder step = generateJob(r, ANSIBLE_DSL_GROOVY_PLAYBOOK_BUILDER) 213 | .getBuildersList() 214 | .get(AnsiblePlaybookBuilder.class); 215 | assertThat("Should add playbook builder", step, notNullValue()); 216 | 217 | assertThat("playbook", step.playbook, is("path/playbook.yml")); 218 | assertThat("extraVar.key", step.extraVars.get(0).getKey(), is("key")); 219 | assertThat("extraVar.value", step.extraVars.get(0).getSecretValue().getPlainText(), is("value")); 220 | assertThat("extraVar.hidden", step.extraVars.get(0).isHidden(), is(true)); 221 | } 222 | 223 | private static FreeStyleProject generateJob(JenkinsRule rule, String script) throws Exception { 224 | FreeStyleProject job = rule.createFreeStyleProject(); 225 | String scriptText = Resources.toString(Resources.getResource(script), StandardCharsets.UTF_8); 226 | 227 | ExecuteDslScripts builder = new ExecuteDslScripts(); 228 | builder.setScriptText(scriptText); 229 | builder.setRemovedJobAction(RemovedJobAction.DELETE); 230 | builder.setRemovedViewAction(RemovedViewAction.DELETE); 231 | builder.setLookupStrategy(LookupStrategy.JENKINS_ROOT); 232 | job.getBuildersList().add(builder); 233 | 234 | rule.buildAndAssertSuccess(job); 235 | 236 | assertThat(rule.getInstance().getJobNames(), hasItem(is(JOB_NAME_IN_DSL_SCRIPT))); 237 | 238 | return rule.getInstance().getItemByFullName(JOB_NAME_IN_DSL_SCRIPT, FreeStyleProject.class); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AbstractAnsibleInvocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 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 com.cloudbees.plugins.credentials.common.StandardCredentials; 20 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 21 | import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; 22 | import hudson.EnvVars; 23 | import hudson.FilePath; 24 | import hudson.Util; 25 | import hudson.model.Run; 26 | import hudson.model.TaskListener; 27 | import hudson.util.ArgumentListBuilder; 28 | import hudson.util.Secret; 29 | import java.io.IOException; 30 | import java.util.HashMap; 31 | import java.util.List; 32 | import java.util.Map; 33 | import java.util.regex.Pattern; 34 | import org.apache.commons.lang3.StringUtils; 35 | import org.jenkinsci.plugins.plaincredentials.FileCredentials; 36 | import org.jenkinsci.plugins.plaincredentials.StringCredentials; 37 | 38 | /** 39 | * Ansible command invocation 40 | */ 41 | abstract class AbstractAnsibleInvocation> { 42 | 43 | protected final EnvVars envVars; 44 | protected final TaskListener listener; 45 | protected final Run build; 46 | protected final Map environment; 47 | 48 | protected String exe; 49 | protected int forks; 50 | protected boolean become; 51 | protected String becomeUser; 52 | protected boolean sudo; 53 | protected String sudoUser; 54 | protected StandardCredentials vaultCredentials; 55 | protected FilePath vaultTmpPath = null; 56 | protected StandardUsernameCredentials credentials; 57 | protected List extraVars; 58 | protected String additionalParameters; 59 | 60 | private FilePath key = null; 61 | private FilePath script = null; 62 | private FilePath vaultPassword = null; 63 | private Inventory inventory; 64 | private boolean copyCredentialsInWorkspace = false; 65 | private final FilePath ws; 66 | 67 | protected AbstractAnsibleInvocation( 68 | String exe, Run build, FilePath ws, TaskListener listener, EnvVars envVars) 69 | throws IOException, InterruptedException, AnsibleInvocationException { 70 | this.build = build; 71 | this.ws = ws; 72 | this.envVars = envVars; 73 | this.environment = new HashMap(this.envVars); 74 | this.listener = listener; 75 | this.exe = exe; 76 | if (exe == null) { 77 | throw new AnsibleInvocationException("Ansible executable not found, check your installation."); 78 | } 79 | } 80 | 81 | protected ArgumentListBuilder appendExecutable(ArgumentListBuilder args) { 82 | args.add(exe); 83 | return args; 84 | } 85 | 86 | public T setInventory(Inventory inventory) { 87 | this.inventory = inventory; 88 | return (T) this; 89 | } 90 | 91 | protected ArgumentListBuilder appendInventory(ArgumentListBuilder args) 92 | throws IOException, InterruptedException, AnsibleInvocationException { 93 | if (inventory == null) { 94 | // throw new AnsibleInvocationException( 95 | // "The inventory of hosts and groups is not defined. Check the job configuration."); 96 | return args; 97 | } 98 | inventory.addArgument(args, ws, envVars, listener); 99 | return args; 100 | } 101 | 102 | public T setForks(int forks) { 103 | this.forks = Math.max(forks, 0); 104 | return (T) this; 105 | } 106 | 107 | public ArgumentListBuilder appendForks(ArgumentListBuilder args) { 108 | if (forks > 0) { 109 | args.add("-f").add(forks); 110 | } 111 | return args; 112 | } 113 | 114 | public T setExtraVars(List extraVars) { 115 | this.extraVars = extraVars; 116 | return (T) this; 117 | } 118 | 119 | public ArgumentListBuilder appendExtraVars(ArgumentListBuilder args) { 120 | if (extraVars != null && !extraVars.isEmpty()) { 121 | for (ExtraVar var : extraVars) { 122 | if (var.getSecretValue() == null) { 123 | listener.getLogger() 124 | .println( 125 | "[WARN] Omitting extra var " + var.getKey() + ": check value is a supported type."); 126 | continue; 127 | } 128 | args.add("-e"); 129 | String value = envVars.expand(var.getSecretValue().getPlainText()); 130 | if (Pattern.compile("\\s").matcher(value).find()) { 131 | value = Util.singleQuote(value); 132 | } 133 | StringBuilder sb = new StringBuilder(); 134 | // assuming Groovy representation for Boolean values 135 | if (value.equals("true") || value.equals("false")) { 136 | // JSON format is required for Boolean variables 137 | sb.append("{\"") 138 | .append(envVars.expand(var.getKey())) 139 | .append("\":") 140 | .append(value) 141 | .append("}"); 142 | } else { 143 | sb.append(envVars.expand(var.getKey())).append("=").append(value); 144 | } 145 | if (var.isHidden()) { 146 | args.addMasked(sb.toString()); 147 | } else { 148 | args.add(sb.toString()); 149 | } 150 | } 151 | } 152 | return args; 153 | } 154 | 155 | public T setAdditionalParameters(String additionalParameters) { 156 | this.additionalParameters = additionalParameters; 157 | return (T) this; 158 | } 159 | 160 | public ArgumentListBuilder appendAdditionalParameters(ArgumentListBuilder args) { 161 | args.addTokenized(envVars.expand(additionalParameters)); 162 | return args; 163 | } 164 | 165 | public T setBecome(boolean become, String becomeUser) { 166 | this.become = become; 167 | this.becomeUser = becomeUser; 168 | return (T) this; 169 | } 170 | 171 | protected ArgumentListBuilder appendBecome(ArgumentListBuilder args) { 172 | if (become) { 173 | args.add("-b"); 174 | addOptionAndValue(args, "--become-user", becomeUser); 175 | } 176 | return args; 177 | } 178 | 179 | public T setSudo(boolean sudo, String sudoUser) { 180 | this.sudo = sudo; 181 | this.sudoUser = sudoUser; 182 | return (T) this; 183 | } 184 | 185 | protected ArgumentListBuilder appendSudo(ArgumentListBuilder args) { 186 | if (sudo) { 187 | args.add("-s"); 188 | addOptionAndValue(args, "-U", sudoUser); 189 | } 190 | return args; 191 | } 192 | 193 | protected void addOptionAndValue(final ArgumentListBuilder args, final String option, final String value) { 194 | if (StringUtils.isNotBlank(value)) { 195 | String expandedValue = envVars.expand(value); 196 | if (StringUtils.isNotBlank(expandedValue)) { 197 | args.add(option).add(expandedValue); 198 | } else { 199 | listener.getLogger() 200 | .println("[WARNING] parameter " + value + " is empty. Omitting option '" + option + "'."); 201 | } 202 | } 203 | } 204 | 205 | protected void addKeyValuePair(final ArgumentListBuilder args, final String key, final String value) { 206 | if (StringUtils.isNotBlank(value)) { 207 | String expandedValue = envVars.expand(value); 208 | if (StringUtils.isNotBlank(expandedValue)) { 209 | args.addKeyValuePair("", key, expandedValue, false); 210 | } else { 211 | listener.getLogger() 212 | .println("[WARNING] parameter " + value + " is empty. Omitting option '" + key + "'."); 213 | } 214 | } 215 | } 216 | 217 | public T setCredentials(StandardUsernameCredentials credentials) { 218 | this.credentials = credentials; 219 | return (T) this; 220 | } 221 | 222 | public T setCredentials(StandardUsernameCredentials credentials, boolean copyCredentialsInWorkspace) { 223 | this.copyCredentialsInWorkspace = copyCredentialsInWorkspace; 224 | return setCredentials(credentials); 225 | } 226 | 227 | public T setVaultCredentials(StandardCredentials vaultCredentials) { 228 | this.vaultCredentials = vaultCredentials; 229 | return (T) this; 230 | } 231 | 232 | public T setVaultTmpPath(FilePath vaultTmpPath) { 233 | this.vaultTmpPath = vaultTmpPath; 234 | return (T) this; 235 | } 236 | 237 | protected ArgumentListBuilder prependPasswordCredentials(ArgumentListBuilder args) { 238 | if (credentials instanceof UsernamePasswordCredentials) { 239 | UsernamePasswordCredentials passwordCredentials = (UsernamePasswordCredentials) credentials; 240 | args.add("sshpass").addMasked("-p" + Secret.toString(passwordCredentials.getPassword())); 241 | } 242 | return args; 243 | } 244 | 245 | protected ArgumentListBuilder appendCredentials(ArgumentListBuilder args) throws IOException, InterruptedException { 246 | if (credentials instanceof SSHUserPrivateKey) { 247 | FilePath tmpPath = vaultTmpPath != null ? vaultTmpPath : ws; 248 | SSHUserPrivateKey privateKeyCredentials = (SSHUserPrivateKey) credentials; 249 | key = Utils.createSshKeyFile(key, tmpPath, privateKeyCredentials, copyCredentialsInWorkspace); 250 | args.add("--private-key").add(key.getRemote().replace("%", "%%")); 251 | args.add("-u").add(privateKeyCredentials.getUsername()); 252 | if (privateKeyCredentials.getPassphrase() != null) { 253 | script = Utils.createSshAskPassFile(script, tmpPath, privateKeyCredentials, copyCredentialsInWorkspace); 254 | environment.put("SSH_ASKPASS", script.getRemote()); 255 | // inspired from https://github.com/jenkinsci/git-client-plugin/pull/168 256 | // but does not work with MacOSX 257 | if (!environment.containsKey("DISPLAY")) { 258 | environment.put("DISPLAY", ":123.456"); 259 | } 260 | } 261 | } else if (credentials instanceof UsernamePasswordCredentials) { 262 | args.add("-u").add(credentials.getUsername()); 263 | args.add("-k"); 264 | } 265 | return args; 266 | } 267 | 268 | protected ArgumentListBuilder appendVaultPasswordFile(ArgumentListBuilder args) 269 | throws IOException, InterruptedException { 270 | if (vaultCredentials != null) { 271 | FilePath tmpPath = vaultTmpPath != null ? vaultTmpPath : ws; 272 | if (vaultCredentials instanceof FileCredentials) { 273 | FileCredentials secretFile = (FileCredentials) vaultCredentials; 274 | vaultPassword = Utils.createVaultPasswordFile(vaultPassword, tmpPath, secretFile); 275 | args.add("--vault-password-file").add(vaultPassword.getRemote().replace("%", "%%")); 276 | } else if (vaultCredentials instanceof StringCredentials) { 277 | StringCredentials secretText = (StringCredentials) vaultCredentials; 278 | vaultPassword = Utils.createVaultPasswordFile(vaultPassword, tmpPath, secretText); 279 | args.add("--vault-password-file").add(vaultPassword.getRemote().replace("%", "%%")); 280 | } 281 | } 282 | return args; 283 | } 284 | 285 | public T setUnbufferedOutput(boolean unbufferedOutput) { 286 | if (unbufferedOutput) { 287 | environment.put("PYTHONUNBUFFERED", "1"); 288 | } 289 | return (T) this; 290 | } 291 | 292 | public T setColorizedOutput(boolean colorizedOutput) { 293 | if (colorizedOutput) { 294 | environment.put("ANSIBLE_FORCE_COLOR", "true"); 295 | } 296 | return (T) this; 297 | } 298 | 299 | public T setDisableHostKeyCheck(boolean disableHostKeyChecking) { 300 | if (disableHostKeyChecking) { 301 | environment.put("ANSIBLE_HOST_KEY_CHECKING", "False"); 302 | } 303 | return (T) this; 304 | } 305 | 306 | protected abstract ArgumentListBuilder buildCommandLine() 307 | throws InterruptedException, AnsibleInvocationException, IOException; 308 | 309 | public boolean execute(CLIRunner runner) throws IOException, InterruptedException, AnsibleInvocationException { 310 | try { 311 | return runner.execute(buildCommandLine(), environment); 312 | } finally { 313 | if (inventory != null) { 314 | inventory.tearDown(listener); 315 | } 316 | Utils.deleteTempFile(key, listener); 317 | Utils.deleteTempFile(script, listener); 318 | Utils.deleteTempFile(vaultPassword, listener); 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 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.plugins.credentials.CredentialsProvider; 19 | import com.cloudbees.plugins.credentials.common.StandardCredentials; 20 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 21 | import edu.umd.cs.findbugs.annotations.NonNull; 22 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 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.Computer; 30 | import hudson.model.Node; 31 | import hudson.model.Run; 32 | import hudson.model.TaskListener; 33 | import hudson.tasks.BuildStepMonitor; 34 | import hudson.tasks.Builder; 35 | import hudson.util.FormValidation; 36 | import java.io.File; 37 | import java.io.IOException; 38 | import java.util.List; 39 | import jenkins.tasks.SimpleBuildStep; 40 | import org.apache.commons.lang3.StringUtils; 41 | import org.kohsuke.stapler.DataBoundConstructor; 42 | import org.kohsuke.stapler.DataBoundSetter; 43 | import org.kohsuke.stapler.QueryParameter; 44 | 45 | /** 46 | * A builder which wraps an Ansible playbook invocation. 47 | */ 48 | public class AnsiblePlaybookBuilder extends Builder implements SimpleBuildStep { 49 | 50 | public final String playbook; 51 | 52 | public final Inventory inventory; 53 | 54 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 55 | public String ansibleName = null; 56 | 57 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 58 | public String limit = null; 59 | 60 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 61 | public String tags = null; 62 | 63 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 64 | public String skippedTags = null; 65 | 66 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 67 | public String startAtTask = null; 68 | 69 | /** 70 | * The id of the credentials to use. 71 | */ 72 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 73 | public String credentialsId = null; 74 | 75 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 76 | public String vaultCredentialsId = null; 77 | 78 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 79 | public String vaultTmpPath = null; 80 | 81 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 82 | public boolean become = false; 83 | 84 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 85 | public String becomeUser = "root"; 86 | 87 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 88 | public boolean checkMode = false; 89 | 90 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 91 | public boolean sudo = false; 92 | 93 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 94 | public String sudoUser = "root"; 95 | 96 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 97 | public int forks = 0; 98 | 99 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 100 | public boolean unbufferedOutput = true; 101 | 102 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 103 | public boolean colorizedOutput = false; 104 | 105 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 106 | public boolean disableHostKeyChecking = false; 107 | 108 | @Deprecated 109 | @SuppressWarnings("unused") 110 | @SuppressFBWarnings(value = {"URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", "PA_PUBLIC_PRIMITIVE_ATTRIBUTE"}) 111 | public transient boolean hostKeyChecking = true; 112 | 113 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 114 | public String additionalParameters = null; 115 | 116 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 117 | public boolean copyCredentialsInWorkspace = false; 118 | 119 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility.") 120 | public List extraVars; 121 | 122 | @Deprecated 123 | public AnsiblePlaybookBuilder( 124 | String ansibleName, 125 | String playbook, 126 | Inventory inventory, 127 | String limit, 128 | String tags, 129 | String skippedTags, 130 | String startAtTask, 131 | String credentialsId, 132 | boolean sudo, 133 | String sudoUser, 134 | int forks, 135 | boolean unbufferedOutput, 136 | boolean colorizedOutput, 137 | boolean hostKeyChecking, 138 | String additionalParameters) { 139 | this.ansibleName = ansibleName; 140 | this.playbook = playbook; 141 | this.inventory = inventory; 142 | this.limit = limit; 143 | this.tags = tags; 144 | this.skippedTags = skippedTags; 145 | this.startAtTask = startAtTask; 146 | this.credentialsId = credentialsId; 147 | this.sudo = sudo; 148 | this.sudoUser = sudoUser; 149 | this.forks = forks; 150 | this.unbufferedOutput = unbufferedOutput; 151 | this.colorizedOutput = colorizedOutput; 152 | // this.hostKeyChecking = hostKeyChecking; 153 | this.additionalParameters = additionalParameters; 154 | } 155 | 156 | @DataBoundConstructor 157 | public AnsiblePlaybookBuilder(String playbook, Inventory inventory) { 158 | this.playbook = playbook; 159 | this.inventory = inventory; 160 | } 161 | 162 | @DataBoundSetter 163 | public void setAnsibleName(String ansibleName) { 164 | this.ansibleName = ansibleName; 165 | } 166 | 167 | @DataBoundSetter 168 | public void setLimit(String limit) { 169 | this.limit = limit; 170 | } 171 | 172 | @DataBoundSetter 173 | public void setTags(String tags) { 174 | this.tags = tags; 175 | } 176 | 177 | @DataBoundSetter 178 | public void setSkippedTags(String skippedTags) { 179 | this.skippedTags = skippedTags; 180 | } 181 | 182 | @DataBoundSetter 183 | public void setStartAtTask(String startAtTask) { 184 | this.startAtTask = startAtTask; 185 | } 186 | 187 | @DataBoundSetter 188 | public void setCredentialsId(String credentialsId) { 189 | setCredentialsId(credentialsId, false); 190 | } 191 | 192 | public void setCredentialsId(String credentialsId, boolean copyCredentialsInWorkspace) { 193 | this.credentialsId = credentialsId; 194 | this.copyCredentialsInWorkspace = copyCredentialsInWorkspace; 195 | } 196 | 197 | @DataBoundSetter 198 | public void setVaultCredentialsId(String vaultCredentialsId) { 199 | this.vaultCredentialsId = vaultCredentialsId; 200 | } 201 | 202 | @DataBoundSetter 203 | public void setVaultTmpPath(String vaultTmpPath) { 204 | this.vaultTmpPath = vaultTmpPath; 205 | } 206 | 207 | public void setBecome(boolean become) { 208 | this.become = become; 209 | } 210 | 211 | @DataBoundSetter 212 | public void setBecomeUser(String becomeUser) { 213 | this.becomeUser = becomeUser; 214 | } 215 | 216 | @DataBoundSetter 217 | public void setSudo(boolean sudo) { 218 | this.sudo = sudo; 219 | } 220 | 221 | @DataBoundSetter 222 | public void setCheckMode(boolean checkMode) { 223 | this.checkMode = checkMode; 224 | } 225 | 226 | @DataBoundSetter 227 | public void setSudoUser(String sudoUser) { 228 | this.sudoUser = sudoUser; 229 | } 230 | 231 | @DataBoundSetter 232 | public void setForks(int forks) { 233 | this.forks = forks; 234 | } 235 | 236 | @DataBoundSetter 237 | public void setUnbufferedOutput(boolean unbufferedOutput) { 238 | this.unbufferedOutput = unbufferedOutput; 239 | } 240 | 241 | @DataBoundSetter 242 | public void setColorizedOutput(boolean colorizedOutput) { 243 | this.colorizedOutput = colorizedOutput; 244 | } 245 | 246 | @DataBoundSetter 247 | public void setDisableHostKeyChecking(boolean disableHostKeyChecking) { 248 | this.disableHostKeyChecking = disableHostKeyChecking; 249 | } 250 | 251 | @DataBoundSetter 252 | @Deprecated 253 | public void setHostKeyChecking(boolean hostKeyChecking) { 254 | this.hostKeyChecking = true; 255 | } 256 | 257 | @DataBoundSetter 258 | public void setAdditionalParameters(String additionalParameters) { 259 | this.additionalParameters = additionalParameters; 260 | } 261 | 262 | @DataBoundSetter 263 | public void setExtraVars(List extraVars) { 264 | this.extraVars = extraVars; 265 | } 266 | 267 | @Override 268 | public void perform( 269 | @NonNull Run run, @NonNull FilePath ws, @NonNull Launcher launcher, @NonNull TaskListener listener) 270 | throws InterruptedException, IOException { 271 | Computer computer = ws.toComputer(); 272 | Node node; 273 | if (computer == null || (node = computer.getNode()) == null) { 274 | throw new AbortException("The ansible playbook build step requires to be launched on a node"); 275 | } 276 | perform(run, node, ws, launcher, listener, run.getEnvironment(listener)); 277 | } 278 | 279 | public void perform( 280 | @NonNull Run run, 281 | @NonNull Node node, 282 | @NonNull FilePath ws, 283 | @NonNull Launcher launcher, 284 | @NonNull TaskListener listener, 285 | EnvVars envVars) 286 | throws InterruptedException, IOException { 287 | try { 288 | CLIRunner runner = new CLIRunner(run, ws, launcher, listener); 289 | Computer computer = node.toComputer(); 290 | String exe = AnsibleInstallation.getExecutable( 291 | ansibleName, AnsibleCommand.ANSIBLE_PLAYBOOK, node, listener, envVars); 292 | AnsiblePlaybookInvocation invocation = new AnsiblePlaybookInvocation(exe, run, ws, listener, envVars); 293 | invocation.setPlaybook(playbook); 294 | invocation.setInventory(inventory); 295 | invocation.setLimit(limit); 296 | invocation.setTags(tags); 297 | invocation.setSkippedTags(skippedTags); 298 | invocation.setStartTask(startAtTask); 299 | invocation.setBecome(become, becomeUser); 300 | invocation.setCheckMode(checkMode); 301 | invocation.setSudo(sudo, sudoUser); 302 | invocation.setForks(forks); 303 | invocation.setCredentials( 304 | StringUtils.isNotBlank(credentialsId) 305 | ? CredentialsProvider.findCredentialById( 306 | run.getEnvironment(listener).expand(credentialsId), 307 | StandardUsernameCredentials.class, 308 | run) 309 | : null, 310 | copyCredentialsInWorkspace); 311 | invocation.setVaultCredentials( 312 | StringUtils.isNotBlank(vaultCredentialsId) 313 | ? CredentialsProvider.findCredentialById( 314 | run.getEnvironment(listener).expand(vaultCredentialsId), 315 | StandardCredentials.class, 316 | run) 317 | : null); 318 | invocation.setVaultTmpPath( 319 | StringUtils.isNotBlank(vaultTmpPath) 320 | ? new FilePath(computer.getChannel(), new File(vaultTmpPath).getAbsolutePath()) 321 | : null); 322 | invocation.setExtraVars(extraVars); 323 | invocation.setAdditionalParameters(additionalParameters); 324 | invocation.setDisableHostKeyCheck(disableHostKeyChecking); 325 | invocation.setUnbufferedOutput(unbufferedOutput); 326 | invocation.setColorizedOutput(colorizedOutput); 327 | if (!invocation.execute(runner)) { 328 | throw new AbortException("Ansible playbook execution failed"); 329 | } 330 | } catch (IOException ioe) { 331 | Util.displayIOException(ioe, listener); 332 | ioe.printStackTrace(listener.fatalError("command execution failed")); 333 | throw ioe; 334 | } catch (AnsibleInvocationException aie) { 335 | listener.fatalError(aie.getMessage()); 336 | throw new AbortException(aie.getMessage()); 337 | } 338 | } 339 | 340 | @Override 341 | public BuildStepMonitor getRequiredMonitorService() { 342 | return BuildStepMonitor.NONE; 343 | } 344 | 345 | @Extension 346 | public static final class DescriptorImpl extends AbstractAnsibleBuilderDescriptor { 347 | public DescriptorImpl() { 348 | super("Invoke Ansible Playbook"); 349 | } 350 | 351 | public FormValidation doCheckPlaybook(@QueryParameter String playbook) { 352 | return checkNotNullOrEmpty(playbook, "Path to playbook must not be empty"); 353 | } 354 | } 355 | } 356 | --------------------------------------------------------------------------------