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