Skip to content

Commit b03df72

Browse files
authored
Merge pull request #169 from jonesbusy/feature/init-integration-tests
Init testcontainer integration tests using Ansible agent
2 parents ccb73a9 + fcb61d0 commit b03df72

23 files changed

+447
-574
lines changed

Diff for: Jenkinsfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
buildPlugin(
66
forkCount: '1C', // run this number of tests in parallel for faster feedback. If the number terminates with a 'C', the value will be multiplied by the number of available CPU cores
7-
useContainerAgent: true, // Set to `false` if you need to use Docker for containerized tests
7+
useContainerAgent: false, // Set to `false` if you need to use Docker for containerized tests
88
configurations: [
99
[platform: 'linux', jdk: 21],
1010
[platform: 'windows', jdk: 17],

Diff for: README.md

+15-114
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Jenkins jobs.
3131

3232
3. Repeat for any additional desired installations
3333

34+
There is no automatic ansible installation possible using Global Tools.
35+
3436
### OS User PATH
3537

3638
Ansible can also be added to the PATH user used by the Jenkins executor
@@ -40,6 +42,18 @@ guide.
4042

4143
------------------------------------------------------------------------
4244

45+
## Supported versions
46+
47+
The plugin is tested against supported ansible-core versions (https://endoflife.date/ansible-core). It might work with older versions, but this is not guaranteed.
48+
49+
See `PipelineTest.java`
50+
51+
```java
52+
private static Stream<String> ansibleVersions() {
53+
return Stream.of("2.14.13", "2.15.8", "2.16.2");
54+
}
55+
```
56+
4357
## Adhoc
4458

4559
[Adhoc commands](http://docs.ansible.com/ansible/latest/intro_adhoc.html) allow
@@ -289,122 +303,9 @@ a "Secret text" or a "Secret file".
289303

290304
------------------------------------------------------------------------
291305

292-
## Open Issues
293-
294-
[View issues in Jira](https://issues.jenkins.io/secure/IssueNavigator.jspa?reset=true&jqlQuery=component%20=%20ansible-plugin%20AND%20status%20in%20%28Open,%20%22In%20Progress%22,%20Reopened%29&tempMax=1000&src=confmacro)
295-
296-
------------------------------------------------------------------------
297-
298306
## Changelog
299307

300-
#### Version 1.0 (26 March 2018)
301-
302-
* [Fix security issue](https://jenkins.io/security/advisory/2018-03-26/#SECURITY-630):
303-
Do not disable host key verification by default. **This may
304-
break existing configurations as host key verification will
305-
be enabled everywhere by default.**
306-
307-
#### Version 0.8.0 (16 Jan 2018)
308-
309-
* Add support for Ansible Vault
310-
[JENKINS-48499](https://issues.jenkins.io/browse/JENKINS-48499)
311-
* Add hostKeyChecking option to pipeline [JENKINS-42445](https://issues.jenkins.io/browse/JENKINS-42445)
312-
313-
#### Version 0.6.2 (3 Jan 2017)
314-
315-
* Fix blocker bug when launched from a pipeline
316-
[JENKINS-40780](https://issues.jenkins.io/browse/JENKINS-40780)
317-
318-
#### Version 0.6.1 (1 Jan 2017)
319-
320-
* Use latest parent project definition in order to deploy
321-
plugin (thanks
322-
to [alecharp](https://github.com/alecharp) for the help and
323-
the PR)
324-
325-
#### Version 0.6 (31 Dec 2016)
326-
327-
**WARN: 0.6.x version will be the last one to support Jenkins 1.xxx and
328-
Ansible 1.x - The 0.7.x and next releases will require Jenkins 2.32.1
329-
(or higher) and Ansible 2.2 (or higher)**
330-
331-
* Add a "do not specify" option for
332-
inventory [JENKINS-34627](https://issues.jenkins.io/browse/JENKINS-34627)
333-
* Support inventoryContent in pipeline (thanks
334-
to [leewin12](https://github.com/leewin12) for the PR)
335-
* Add support of extra variables in jobdsl (thanks
336-
to [pawbur](https://github.com/pawbur) for the PR)
337-
* Support empty forks (number of parallel processes)
338-
parameter [JENKINS-39438](https://issues.jenkins.io/browse/JENKINS-39438)
339-
* Escape '%' character in private key path (thanks
340-
to [ewollesen](https://github.com/ewollesen) for the PR)
341-
* Omit ansible option when expanded environment variable is
342-
empty (thanks to [vjestin](https://github.com/vjestin) for
343-
the PR)
344-
* Add the --forks parameter configurable in pipeline step
345-
(thanks to
346-
[anguswilliams](https://github.com/anguswilliams) for the
347-
PR)
348-
* Fix usage of environment variable in ansiblePlaybook
349-
pipeline step (thanks to
350-
[thomasKalmar](https://github.com/thomasKalmar)
351-
and [barthorre](https://github.com/barthorre) for the
352-
PR) [JENKINS-38289](https://issues.jenkins.io/browse/JENKINS-38289)
353-
354-
#### Version 0.5 (5 May 2016)
355-
356-
* Add support for ansible extra variables
357-
[JENKINS-29863](https://issues.jenkins.io/browse/JENKINS-29863)
358-
* Improve Pipeline plugin
359-
integration [JENKINS-32911](https://issues.jenkins.io/browse/JENKINS-32911)
360-
* Add the possibility to use the default inventory file
361-
(thanks to Johann Schmitz for the PR)
362-
* Add colorized output in pipeline jobs (thanks to
363-
Kirill Merkushev for the PR)
364-
* Make Jenkins build variables available as environment
365-
variables for ansible (thanks to Kevin Mooney for the
366-
PR) [JENKINS-29284](https://issues.jenkins.io/browse/JENKINS-29284)
367-
368-
#### Version 0.4 (25 December 2015)
369-
370-
* Support for password protected SSH
371-
keys [JENKINS-30656](https://issues.jenkins.io/browse/JENKINS-30656)
372-
* Initial support for the workflow
373-
plugin [JENKINS-30398](https://issues.jenkins.io/browse/JENKINS-30398)
374-
* Add support for Job DSL plugin (thanks to Kirill Merkushev
375-
for the
376-
PR) [JENKINS-31790](https://issues.jenkins.io/browse/JENKINS-31790)
377-
378-
#### Version 0.3.1 (15 July 2015)
379-
380-
* Fix execution on slave
381-
nodes [JENKINS-29294](https://issues.jenkins.io/browse/JENKINS-29294)
382-
383-
#### Version 0.3 (20 June 2015)
384-
385-
* Add support for password based SSH authentication (with
386-
sshpass)
387-
* Environment variables can be used in Module and Module
388-
arguments text field in Ad-hoc command builder
389-
* Environment variables can be used in inline inventory text
390-
box
391-
[JENKINS-28547](https://issues.jenkins.io/browse/JENKINS-28547)
392-
393-
#### Version 0.2 (11 May 2015)
394-
395-
* Fix NullPointerException when no credentials are selected
396-
* Fix --skippedTags parameter configuration which was ignored
397-
* Fix NullPointerException and print an error message in the
398-
build console when the inventory is not set in the job
399-
configuration
400-
401-
#### Version 0.1 (01 May 2015)
402-
403-
* Initial version
404-
405-
This plugin gives the possibility to run [Ansible](http://www.ansible.com/) ad-hoc command or playbooks as a build step.
406-
407-
[![Build Status](https://ci.jenkins.io/buildStatus/icon?job=Plugins/ansible-plugin/master)](https://ci.jenkins.io/job/Plugins/job/ansible-plugin/job/master/)
308+
Changelog is now published on GitHub release.
408309

409310
## Using Jenkins Build and Environment Variables
410311

Diff for: pom.xml

+33-4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
<gitHubRepo>jenkinsci/ansible-plugin</gitHubRepo>
3131
<jenkins.version>2.426.2</jenkins.version>
3232
<spotless.check.skip>false</spotless.check.skip>
33+
34+
<!-- Test dependencies version -->
35+
<testcontainer.version>1.19.3</testcontainer.version>
36+
3337
</properties>
3438
<dependencyManagement>
3539
<dependencies>
@@ -66,12 +70,12 @@
6670
<groupId>org.jenkins-ci.plugins</groupId>
6771
<artifactId>ssh-credentials</artifactId>
6872
</dependency>
73+
<!-- Test dependencies -->
6974
<dependency>
7075
<groupId>org.jenkins-ci.plugins.workflow</groupId>
7176
<artifactId>workflow-step-api</artifactId>
7277
<optional>true</optional>
7378
</dependency>
74-
<!-- Test -->
7579
<dependency>
7680
<groupId>junit</groupId>
7781
<artifactId>junit</artifactId>
@@ -83,16 +87,15 @@
8387
<scope>test</scope>
8488
</dependency>
8589
<dependency>
86-
<groupId>org.jenkins-ci.plugins.workflow</groupId>
87-
<artifactId>workflow-basic-steps</artifactId>
90+
<groupId>org.jenkins-ci.plugins</groupId>
91+
<artifactId>ssh-slaves</artifactId>
8892
<scope>test</scope>
8993
</dependency>
9094
<dependency>
9195
<groupId>org.jenkins-ci.plugins.workflow</groupId>
9296
<artifactId>workflow-cps</artifactId>
9397
<scope>test</scope>
9498
</dependency>
95-
<!-- Test plugins -->
9699
<dependency>
97100
<groupId>org.jenkins-ci.plugins.workflow</groupId>
98101
<artifactId>workflow-job</artifactId>
@@ -108,6 +111,32 @@
108111
<artifactId>mockito-core</artifactId>
109112
<scope>test</scope>
110113
</dependency>
114+
<dependency>
115+
<groupId>org.testcontainers</groupId>
116+
<artifactId>junit-jupiter</artifactId>
117+
<version>${testcontainer.version}</version>
118+
<scope>test</scope>
119+
</dependency>
120+
<dependency>
121+
<groupId>org.testcontainers</groupId>
122+
<artifactId>testcontainers</artifactId>
123+
<version>${testcontainer.version}</version>
124+
<scope>test</scope>
125+
<exclusions>
126+
<exclusion>
127+
<groupId>org.apache.commons</groupId>
128+
<artifactId>commons-compress</artifactId>
129+
</exclusion>
130+
<exclusion>
131+
<groupId>org.jetbrains</groupId>
132+
<artifactId>annotations</artifactId>
133+
</exclusion>
134+
<exclusion>
135+
<groupId>org.slf4j</groupId>
136+
<artifactId>slf4j-api</artifactId>
137+
</exclusion>
138+
</exclusions>
139+
</dependency>
111140
</dependencies>
112141
<repositories>
113142
<repository>

Diff for: src/main/java/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandBuilder.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,9 @@ public void perform(
239239
? CredentialsProvider.findCredentialById(vaultCredentialsId, StandardCredentials.class, run)
240240
: null);
241241
invocation.setVaultTmpPath(
242-
StringUtils.isNotBlank(vaultTmpPath) ? new FilePath(new File(vaultTmpPath)) : null);
242+
StringUtils.isNotBlank(vaultTmpPath)
243+
? new FilePath(computer.getChannel(), new File(vaultTmpPath).getAbsolutePath())
244+
: null);
243245
invocation.setExtraVars(extraVars);
244246
invocation.setAdditionalParameters(additionalParameters);
245247
invocation.setDisableHostKeyCheck(disableHostKeyChecking);

Diff for: src/main/java/org/jenkinsci/plugins/ansible/AnsiblePlaybookBuilder.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ public void perform(
266266
throws InterruptedException, IOException {
267267
try {
268268
CLIRunner runner = new CLIRunner(run, ws, launcher, listener);
269+
Computer computer = node.toComputer();
269270
String exe = AnsibleInstallation.getExecutable(
270271
ansibleName, AnsibleCommand.ANSIBLE_PLAYBOOK, node, listener, envVars);
271272
AnsiblePlaybookInvocation invocation = new AnsiblePlaybookInvocation(exe, run, ws, listener, envVars);
@@ -295,7 +296,9 @@ public void perform(
295296
run)
296297
: null);
297298
invocation.setVaultTmpPath(
298-
StringUtils.isNotBlank(vaultTmpPath) ? new FilePath(new File(vaultTmpPath)) : null);
299+
StringUtils.isNotBlank(vaultTmpPath)
300+
? new FilePath(computer.getChannel(), new File(vaultTmpPath).getAbsolutePath())
301+
: null);
299302
invocation.setExtraVars(extraVars);
300303
invocation.setAdditionalParameters(additionalParameters);
301304
invocation.setDisableHostKeyCheck(disableHostKeyChecking);

Diff for: src/main/java/org/jenkinsci/plugins/ansible/AnsibleVaultBuilder.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public void perform(
125125
throws InterruptedException, IOException {
126126
try {
127127
CLIRunner runner = new CLIRunner(run, ws, launcher, listener);
128+
Computer computer = node.toComputer();
128129
String exe = AnsibleInstallation.getExecutable(
129130
ansibleName, AnsibleCommand.ANSIBLE_VAULT, node, listener, envVars);
130131
AnsibleVaultInvocation invocation = new AnsibleVaultInvocation(exe, run, ws, listener, envVars);
@@ -144,7 +145,9 @@ public void perform(
144145
run)
145146
: null);
146147
invocation.setVaultTmpPath(
147-
StringUtils.isNotBlank(vaultTmpPath) ? new FilePath(new File(vaultTmpPath)) : null);
148+
StringUtils.isNotBlank(vaultTmpPath)
149+
? new FilePath(computer.getChannel(), new File(vaultTmpPath).getAbsolutePath())
150+
: null);
148151
invocation.setContent(content);
149152
invocation.setInput(input);
150153
invocation.setOutput(output);

Diff for: src/main/java/org/jenkinsci/plugins/ansible/Utils.java

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ static FilePath createSshKeyFile(FilePath key, FilePath tmpPath, SSHUserPrivateK
5050
static FilePath createSshAskPassFile(
5151
FilePath script, FilePath tmpPath, SSHUserPrivateKey credentials, boolean inThisDir)
5252
throws IOException, InterruptedException {
53+
tmpPath.mkdirs();
5354
StringBuilder sb = new StringBuilder();
5455
sb.append("#! /bin/sh\n").append("/bin/echo \"" + Secret.toString(credentials.getPassphrase()) + "\"");
5556
script = tmpPath.createTextTempFile("ssh", ".sh", sb.toString(), inThisDir);
@@ -69,6 +70,7 @@ static FilePath createSshAskPassFile(
6970
static FilePath createVaultPasswordFile(FilePath key, FilePath tmpPath, FileCredentials credentials)
7071
throws IOException, InterruptedException {
7172
try (InputStream content = credentials.getContent()) {
73+
tmpPath.mkdirs();
7274
key = tmpPath.createTempFile("vault", ".password");
7375
key.copyFrom(content);
7476
key.chmod(0400);
@@ -87,6 +89,7 @@ static FilePath createVaultPasswordFile(FilePath key, FilePath tmpPath, FileCred
8789
*/
8890
static FilePath createVaultPasswordFile(FilePath key, FilePath tmpPath, StringCredentials credentials)
8991
throws IOException, InterruptedException {
92+
tmpPath.mkdirs();
9093
key = tmpPath.createTextTempFile(
9194
"vault", ".password", credentials.getSecret().getPlainText(), true);
9295
key.chmod(0400);

Diff for: src/test/java/org/jenkinsci/plugins/ansible/AnsibleAdHocCommandInvocationTest.java

-64
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,12 @@
66
import static org.hamcrest.Matchers.hasEntry;
77
import static org.mockito.Mockito.*;
88

9-
import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey;
10-
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
119
import hudson.EnvVars;
1210
import hudson.model.AbstractBuild;
1311
import hudson.model.BuildListener;
1412
import hudson.model.TaskListener;
1513
import hudson.util.ArgumentListBuilder;
16-
import hudson.util.Secret;
1714
import java.util.Map;
18-
import org.junit.Ignore;
1915
import org.junit.Test;
2016
import org.mockito.ArgumentCaptor;
2117

@@ -128,66 +124,6 @@ public void secure_by_default_SEC_630() throws Exception {
128124
assertThat((Map<String, String>) argument.getValue(), hasEntry("ANSIBLE_FORCE_COLOR", "true"));
129125
}
130126

131-
@Test
132-
@Ignore("build.getWorkspace() cannot be mocked")
133-
public void should_handle_private_key_credentials() throws Exception {
134-
// Given
135-
Inventory inventory = new InventoryPath("/tmp/hosts");
136-
SSHUserPrivateKey pkey = mock(SSHUserPrivateKey.class);
137-
when(pkey.getUsername()).thenReturn("mylogin");
138-
BuildListener listener = mock(BuildListener.class);
139-
CLIRunner runner = mock(CLIRunner.class);
140-
AbstractBuild<?, ?> build = mock(AbstractBuild.class);
141-
when(build.getEnvironment(any(TaskListener.class))).thenReturn(new EnvVars());
142-
AnsibleAdHocCommandInvocation invocation =
143-
new AnsibleAdHocCommandInvocation("/usr/local/bin/ansible", build, listener);
144-
invocation.setHostPattern("localhost");
145-
invocation.setInventory(inventory);
146-
invocation.setModule("ping");
147-
invocation.setCredentials(pkey);
148-
invocation.setForks(5);
149-
// When
150-
invocation.execute(runner);
151-
// Then
152-
ArgumentCaptor<ArgumentListBuilder> argument = ArgumentCaptor.forClass(ArgumentListBuilder.class);
153-
verify(runner).execute(argument.capture(), anyMap());
154-
155-
assertThat(
156-
argument.getValue().toString(),
157-
is("/usr/local/bin/ansible localhost -i /tmp/hosts -m ping -f 5 --private-key .+ -u mylogin"));
158-
}
159-
160-
@Test
161-
@Ignore("Secret can neither be instanced nor mocked")
162-
public void should_handle_password_credentials() throws Exception {
163-
// Given
164-
Inventory inventory = new InventoryPath("/tmp/hosts");
165-
StandardUsernamePasswordCredentials password = mock(StandardUsernamePasswordCredentials.class);
166-
when(password.getUsername()).thenReturn("mylogin");
167-
when(password.getPassword()).thenReturn(Secret.fromString("aStrongSecretPassword"));
168-
BuildListener listener = mock(BuildListener.class);
169-
CLIRunner runner = mock(CLIRunner.class);
170-
AbstractBuild<?, ?> build = mock(AbstractBuild.class);
171-
when(build.getEnvironment(any(TaskListener.class))).thenReturn(new EnvVars());
172-
AnsibleAdHocCommandInvocation invocation =
173-
new AnsibleAdHocCommandInvocation("/usr/local/bin/ansible", build, listener);
174-
invocation.setHostPattern("localhost");
175-
invocation.setInventory(inventory);
176-
invocation.setModule("ping");
177-
invocation.setCredentials(password);
178-
invocation.setForks(5);
179-
// When
180-
invocation.execute(runner);
181-
// Then
182-
ArgumentCaptor<ArgumentListBuilder> argument = ArgumentCaptor.forClass(ArgumentListBuilder.class);
183-
verify(runner).execute(argument.capture(), anyMap());
184-
185-
assertThat(
186-
argument.getValue().toString(),
187-
is("sshpass ****** /usr/local/bin/ansible localhost -i /tmp/hosts -m ping -f 5 " + "-u"
188-
+ " mylogin -k"));
189-
}
190-
191127
@Test
192128
public void should_handle_variables() throws Exception {
193129
// Given

0 commit comments

Comments
 (0)