Skip to content

Commit 69de084

Browse files
authored
Merge pull request #720 from martinreck/feature/username-password-credentials
2 parents 4d4538b + 7030b72 commit 69de084

File tree

9 files changed

+165
-46
lines changed

9 files changed

+165
-46
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,40 @@ Please refer to the `BasePipelineTest` class for the list of currently supported
217217
Some tricky methods such as `load` and `parallel` are implemented directly in the helper.
218218
If you want to override those, make sure that you extend the `PipelineTestHelper` class.
219219

220+
### Mocking Jenkins Credentials
221+
222+
You can create mock credentials for use in the pipeline.
223+
The provided mock methods can be used to simulate sensitive data, such as access keys or user login details.
224+
225+
```groovy
226+
// Jenkinsfile
227+
node {
228+
stage('Process with credentials') {
229+
withCredentials([usernamePassword(credentialsId: 'credentials-1', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
230+
echo 'User/Password: ${USERNAME}/${PASSWORD}'
231+
}
232+
withCredentials([string(credentialsId: 'credentials-2', variable: 'TOKEN')]) {
233+
echo 'Token: ${TOKEN}'
234+
}
235+
}
236+
}
237+
```
238+
239+
```groovy
240+
import com.lesfurets.jenkins.unit.BasePipelineTest
241+
242+
class TestExampleJob extends BasePipelineTest {
243+
@Test
244+
void exampleWithCredentialsTest() {
245+
// Simulates a username and password
246+
addUsernamePasswordCredential('credentials-1', 'admin', 'very-strong-password')
247+
248+
// Simulates a secret text
249+
addStringCredential('credentials-2', 'secret-token')
250+
}
251+
}
252+
```
253+
220254
### Mocking `readFile` and `fileExists`
221255

222256
The `readFile` and `fileExists` steps can be mocked to return a specific result for a

src/main/groovy/com/lesfurets/jenkins/unit/BasePipelineTest.groovy

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,29 @@ abstract class BasePipelineTest {
2525
addParam(desc.name, desc.defaultValue, false)
2626
}
2727

28-
def stringInterceptor = { Map desc->
28+
def stringInterceptor = { Map desc ->
2929
if (desc) {
3030
// we are in context of parameters { string(...)}
3131
if (desc.name) {
3232
addParam(desc.name, desc.defaultValue, false)
3333
}
3434
// we are in context of withCredentials([string()..]) { }
35-
if(desc.variable) {
36-
return desc.variable
35+
if(desc.credentialsId && desc.variable) {
36+
return [
37+
credentialsType: 'string',
38+
credentialsId: desc.credentialsId,
39+
variable: desc.variable
40+
]
3741
}
3842
}
3943
}
4044

41-
def usernamePasswordInterceptor = { m -> [m.usernameVariable, m.passwordVariable] }
45+
def usernamePasswordInterceptor = { m -> [
46+
credentialsType: 'usernamePassword',
47+
credentialsId: m.credentialsId,
48+
usernameVariable: m.usernameVariable,
49+
passwordVariable: m.passwordVariable
50+
]}
4251

4352
def withCredentialsInterceptor = { list, closure ->
4453
def previousValues = [:]
@@ -67,6 +76,9 @@ abstract class BasePipelineTest {
6776
previousValues[password] = null
6877
}
6978
binding.setVariable(password, password)
79+
} else if (creds instanceof Map && creds.get("credentialsType")) {
80+
// Delegate handling of known credential types
81+
handleCredentialBinding(creds, previousValues)
7082
} else {
7183
creds.each { var ->
7284
try {
@@ -560,4 +572,60 @@ abstract class BasePipelineTest {
560572
Map credentials = binding.getVariable('credentials') as Map
561573
credentials[key] = val
562574
}
575+
576+
void addUsernamePasswordCredential(String credentialId, String username, String password) {
577+
addCredential(credentialId, "${username}:${password}")
578+
}
579+
580+
void addStringCredential(String credentialId, String val) {
581+
addCredential(credentialId, val)
582+
}
583+
584+
/**
585+
* Handles the processing and binding for different types of credentials dynamically.
586+
*/
587+
void handleCredentialBinding(Map creds, Map previousValues) {
588+
def credentialsId = creds.get('credentialsId')
589+
def credentials = binding.getVariable('credentials')
590+
if (credentials == null || !credentials.containsKey(credentialsId)) {
591+
throw new AssertionError("The configuration for the credentials ID '${credentialsId}' may be missing.", null)
592+
}
593+
594+
switch (creds.get('credentialsType')) {
595+
case 'usernamePassword':
596+
def usernamePassword = credentials.get(credentialsId).split(':')
597+
def usernameVariable = creds.get('usernameVariable')
598+
def passwordVariable = creds.get('passwordVariable')
599+
600+
try {
601+
previousValues[usernameVariable] = binding.getVariable(usernameVariable)
602+
} catch (MissingPropertyException ignored) {
603+
previousValues[usernameVariable] = null
604+
}
605+
binding.setVariable(usernameVariable, usernamePassword[0])
606+
607+
try {
608+
previousValues[passwordVariable] = binding.getVariable(passwordVariable)
609+
} catch (MissingPropertyException ignored) {
610+
previousValues[passwordVariable] = null
611+
}
612+
binding.setVariable(passwordVariable, usernamePassword[1])
613+
break
614+
615+
case 'string':
616+
def variable = creds.get('variable')
617+
def value = credentials.get(credentialsId)
618+
619+
try {
620+
previousValues[variable] = binding.getVariable(variable)
621+
} catch (MissingPropertyException ignored) {
622+
previousValues[variable] = null
623+
}
624+
binding.setVariable(variable, value)
625+
break
626+
627+
default:
628+
throw new UnsupportedOperationException("Unsupported credentials type: ${creds.get('credentialsType')}")
629+
}
630+
}
563631
}

src/test/groovy/com/lesfurets/jenkins/TestWithCredentialsAndParametersJob.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ class TestWithCredentialsAndParametersJob extends BaseRegressionTest {
1515

1616
@Test
1717
void should_run_script_with_parameters() {
18+
// given:
19+
addStringCredential('my-gitlab-api-token', 'gitlab-api-token')
20+
1821
// when:
1922
runScript("job/withCredentialsAndParameters.jenkins")
2023

src/test/groovy/com/lesfurets/jenkins/TestWithCredentialsJob.groovy

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ class TestWithCredentialsJob extends BaseRegressionTest {
1717

1818
@Test
1919
void should_run_script_with_credentials() {
20+
// given:
21+
addUsernamePasswordCredential('my_cred_id-1', 'test-user-1', 'test-password-1')
22+
addUsernamePasswordCredential('my_cred_id-2', 'test-user-2', 'test-password-2')
23+
addStringCredential('docker_cred-1', 'docker-pass-1')
24+
addStringCredential('docker_cred-2', 'docker-pass-2')
25+
addStringCredential('ssh_cred-1', 'ssh-pass-1')
26+
addStringCredential('ssh_cred-2', 'ssh-pass-2')
27+
2028
// when:
2129
runScript("job/withCredentials.jenkins")
2230

src/test/groovy/com/lesfurets/jenkins/unit/declarative/TestDeclaraticeWithCredentials.groovy

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,32 @@ class TestDeclaraticeWithCredentials extends DeclarativePipelineTest {
1818

1919
@Test
2020
void should_run_script_with_credentials() {
21+
// given:
22+
addUsernamePasswordCredential('my_cred_id-1', 'test-user-1', 'test-password-1')
23+
addUsernamePasswordCredential('my_cred_id-2', 'test-user-2', 'test-password-2')
24+
addStringCredential('docker_cred-1', 'docker-pass-1')
25+
addStringCredential('docker_cred-2', 'docker-pass-2')
26+
addStringCredential('ssh_cred-1', 'ssh-pass-1')
27+
addStringCredential('ssh_cred-2', 'ssh-pass-2')
28+
2129
// when:
2230
runScript("withCredentials_Jenkinsfile")
2331

2432
// then:
2533
assertJobStatusSuccess()
26-
assertCallStack().contains("echo(User/Pass = user/pass)")
27-
assertCallStack().contains("echo(Nested User/Pass = user/pass)")
28-
assertCallStack().contains("echo(Restored User/Pass = user/pass)")
34+
assertCallStack().contains("echo(User/Pass = test-user-1/test-password-1)")
35+
assertCallStack().contains("echo(Nested User/Pass = test-user-2/test-password-2)")
36+
assertCallStack().contains("echo(Restored User/Pass = test-user-1/test-password-1)")
2937
assertCallStack().contains("echo(Cleared User/Pass = null/null)")
3038

31-
assertCallStack().contains("echo(Docker = docker_pass)")
32-
assertCallStack().contains("echo(Nested Docker = docker_pass)")
33-
assertCallStack().contains("echo(Restored Docker = docker_pass)")
39+
assertCallStack().contains("echo(Docker = docker-pass-1)")
40+
assertCallStack().contains("echo(Nested Docker = docker-pass-2)")
41+
assertCallStack().contains("echo(Restored Docker = docker-pass-1)")
3442
assertCallStack().contains("echo(Cleared Docker = null)")
3543

36-
assertCallStack().contains("echo(SSH = ssh_pass)")
37-
assertCallStack().contains("echo(Nested SSH = ssh_pass)")
38-
assertCallStack().contains("echo(Restored SSH = ssh_pass)")
44+
assertCallStack().contains("echo(SSH = ssh-pass-1)")
45+
assertCallStack().contains("echo(Nested SSH = ssh-pass-2)")
46+
assertCallStack().contains("echo(Restored SSH = ssh-pass-1)")
3947
assertCallStack().contains("echo(Cleared SSH = null)")
40-
41-
4248
}
4349
}

src/test/jenkins/jenkinsfiles/withCredentials_Jenkinsfile

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@ pipeline {
44
stages {
55
stage("run") {
66
withCredentials([
7-
usernamePassword( credentialsId: 'my_cred_id', usernameVariable: 'user', passwordVariable: 'pass' ),
8-
string( credentialsId: 'docker_cred', variable: 'docker_pass' ),
9-
string( credentialsId: 'ssh_cred', variable: 'ssh_pass' )
7+
usernamePassword( credentialsId: 'my_cred_id-1', usernameVariable: 'user', passwordVariable: 'pass' ),
8+
string( credentialsId: 'docker_cred-1', variable: 'docker_pass' ),
9+
string( credentialsId: 'ssh_cred-1', variable: 'ssh_pass' )
1010
]) {
1111
// Ensure values are set on entry
1212
echo "User/Pass = $user/$pass"
1313
echo "Docker = $docker_pass"
1414
echo "SSH = $ssh_pass"
1515

1616
withCredentials([
17-
usernamePassword( credentialsId: 'my_cred_id', usernameVariable: 'user', passwordVariable: 'pass' ),
18-
string( credentialsId: 'docker_cred', variable: 'docker_pass' ),
19-
string( credentialsId: 'ssh_cred', variable: 'ssh_pass' )
17+
usernamePassword( credentialsId: 'my_cred_id-2', usernameVariable: 'user', passwordVariable: 'pass' ),
18+
string( credentialsId: 'docker_cred-2', variable: 'docker_pass' ),
19+
string( credentialsId: 'ssh_cred-2', variable: 'ssh_pass' )
2020
]) {
2121
// Ensure nested values are in place
2222
echo "Nested User/Pass = $user/$pass"

src/test/jenkins/job/withCredentials.jenkins

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
node {
22
withCredentials([
3-
usernamePassword( credentialsId: 'my_cred_id', usernameVariable: 'user', passwordVariable: 'pass' ),
4-
string( credentialsId: 'docker_cred', variable: 'docker_pass' ),
5-
string( credentialsId: 'ssh_cred', variable: 'ssh_pass' )
3+
usernamePassword( credentialsId: 'my_cred_id-1', usernameVariable: 'user', passwordVariable: 'pass' ),
4+
string( credentialsId: 'docker_cred-1', variable: 'docker_pass' ),
5+
string( credentialsId: 'ssh_cred-1', variable: 'ssh_pass' )
66
]) {
77
// Ensure values are set on entry
88
echo "User/Pass = $user/$pass"
99
echo "Docker = $docker_pass"
1010
echo "SSH = $ssh_pass"
1111

1212
withCredentials([
13-
usernamePassword( credentialsId: 'my_cred_id', usernameVariable: 'user', passwordVariable: 'pass' ),
14-
string( credentialsId: 'docker_cred', variable: 'docker_pass' ),
15-
string( credentialsId: 'ssh_cred', variable: 'ssh_pass' )
13+
usernamePassword( credentialsId: 'my_cred_id-2', usernameVariable: 'user', passwordVariable: 'pass' ),
14+
string( credentialsId: 'docker_cred-2', variable: 'docker_pass' ),
15+
string( credentialsId: 'ssh_cred-2', variable: 'ssh_pass' )
1616
]) {
1717
// Ensure nested values are in place
1818
echo "Nested User/Pass = $user/$pass"

src/test/resources/callstacks/TestWithCredentialsAndParametersJob_withCredentialsAndParameters.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
withCredentialsAndParameters.properties([null])
66
withCredentialsAndParameters.echo('myStringParam' value is default: my default value)
77
withCredentialsAndParameters.string({credentialsId=my-gitlab-api-token, variable=GITLAB_API_TOKEN})
8-
withCredentialsAndParameters.withCredentials([GITLAB_API_TOKEN], groovy.lang.Closure)
9-
withCredentialsAndParameters.echo('my-gitlab-api-token' credential variable value: GITLAB_API_TOKEN)
8+
withCredentialsAndParameters.withCredentials([{credentialsType=string, credentialsId=my-gitlab-api-token, variable=GITLAB_API_TOKEN}], groovy.lang.Closure)
9+
withCredentialsAndParameters.echo('my-gitlab-api-token' credential variable value: gitlab-api-token)
Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
withCredentials.run()
22
withCredentials.node(groovy.lang.Closure)
3-
withCredentials.usernamePassword({credentialsId=my_cred_id, usernameVariable=user, passwordVariable=pass})
4-
withCredentials.string({credentialsId=docker_cred, variable=docker_pass})
5-
withCredentials.string({credentialsId=ssh_cred, variable=ssh_pass})
6-
withCredentials.withCredentials([[user, pass], docker_pass, ssh_pass], groovy.lang.Closure)
7-
withCredentials.echo(User/Pass = user/pass)
8-
withCredentials.echo(Docker = docker_pass)
9-
withCredentials.echo(SSH = ssh_pass)
10-
withCredentials.usernamePassword({credentialsId=my_cred_id, usernameVariable=user, passwordVariable=pass})
11-
withCredentials.string({credentialsId=docker_cred, variable=docker_pass})
12-
withCredentials.string({credentialsId=ssh_cred, variable=ssh_pass})
13-
withCredentials.withCredentials([[user, pass], docker_pass, ssh_pass], groovy.lang.Closure)
14-
withCredentials.echo(Nested User/Pass = user/pass)
15-
withCredentials.echo(Nested Docker = docker_pass)
16-
withCredentials.echo(Nested SSH = ssh_pass)
17-
withCredentials.echo(Restored User/Pass = user/pass)
18-
withCredentials.echo(Restored Docker = docker_pass)
19-
withCredentials.echo(Restored SSH = ssh_pass)
3+
withCredentials.usernamePassword({credentialsId=my_cred_id-1, usernameVariable=user, passwordVariable=pass})
4+
withCredentials.string({credentialsId=docker_cred-1, variable=docker_pass})
5+
withCredentials.string({credentialsId=ssh_cred-1, variable=ssh_pass})
6+
withCredentials.withCredentials([{credentialsType=usernamePassword, credentialsId=my_cred_id-1, usernameVariable=user, passwordVariable=pass}, {credentialsType=string, credentialsId=docker_cred-1, variable=docker_pass}, {credentialsType=string, credentialsId=ssh_cred-1, variable=ssh_pass}], groovy.lang.Closure)
7+
withCredentials.echo(User/Pass = test-user-1/test-password-1)
8+
withCredentials.echo(Docker = docker-pass-1)
9+
withCredentials.echo(SSH = ssh-pass-1)
10+
withCredentials.usernamePassword({credentialsId=my_cred_id-2, usernameVariable=user, passwordVariable=pass})
11+
withCredentials.string({credentialsId=docker_cred-2, variable=docker_pass})
12+
withCredentials.string({credentialsId=ssh_cred-2, variable=ssh_pass})
13+
withCredentials.withCredentials([{credentialsType=usernamePassword, credentialsId=my_cred_id-2, usernameVariable=user, passwordVariable=pass}, {credentialsType=string, credentialsId=docker_cred-2, variable=docker_pass}, {credentialsType=string, credentialsId=ssh_cred-2, variable=ssh_pass}], groovy.lang.Closure)
14+
withCredentials.echo(Nested User/Pass = test-user-2/test-password-2)
15+
withCredentials.echo(Nested Docker = docker-pass-2)
16+
withCredentials.echo(Nested SSH = ssh-pass-2)
17+
withCredentials.echo(Restored User/Pass = test-user-1/test-password-1)
18+
withCredentials.echo(Restored Docker = docker-pass-1)
19+
withCredentials.echo(Restored SSH = ssh-pass-1)
2020
withCredentials.echo(Cleared User/Pass = null/null)
2121
withCredentials.echo(Cleared Docker = null)
2222
withCredentials.echo(Cleared SSH = null)

0 commit comments

Comments
 (0)