Sometimes, working with variables, especially ones containing special characters (like $), we need to be more careful.
NOTE: If you want to test the following, you will need a machine with Docker and Jenkins installed.
Let’s take for an example this pipeline in Jenkins:
pipeline {
agent any
environment
{
PASS='Pa$$word'
}
stages {
stage('Clean') {
steps {
sh 'docker container rm --force $(docker container ls -aq) || true'
}
}
stage('Escape Test #1 - NOK') {
steps {
sh 'docker container run -d --name db1 -e MYSQL_ROOT_PASSWORD=Pa$$word mariadb'
}
}
stage('Escape Test #2 - NOK') {
steps {
sh "docker container run -d --name db2 -e MYSQL_ROOT_PASSWORD=Pa\$\$word mariadb"
}
}
stage('Escape Test #3 - OK') {
steps {
sh "docker container run -d --name db3 -e MYSQL_ROOT_PASSWORD='Pa\$\$word' mariadb"
}
}
stage('Escape Test #4 - OK') {
steps {
sh 'docker container run -d --name db4 -e MYSQL_ROOT_PASSWORD=\'Pa\$\$word\' mariadb'
}
}
stage('Escape Test #5 - OK') {
steps {
sh '''docker container run -d --name db5 -e MYSQL_ROOT_PASSWORD='Pa$$word' mariadb'''
}
}
stage('Escape Test #6 - OK') {
steps {
sh 'docker container run -d --name db6 -e MYSQL_ROOT_PASSWORD=$PASS mariadb'
}
}
}
}
NOTE: MYSQL_ROOT_PASSWORD in the above example can be replaced with MARIADB_ROOT_PASSWORD.
At each step, a MariaDB container is started. he difference is in how the root user password is provided. In all cases, it is expected that a password with the content Pa$$word is provided.
When the pipeline is executed, it will complete successfully with no obvious indication of a potential problem, at least not at first glance. Yes, but no.
If we check in a terminal session whether we are able to communicate with the database using the password we set, we will find that things are not quite as we expect.
Approach 1 - nonworking:
$ docker container exec db1 mariadb -u root -p'Pa$$word' -e 'SHOW DATABASES;'
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
$
Approach 2 - nonworking:
$ docker container exec db2 mariadb -u root -p'Pa$$word' -e 'SHOW DATABASES;'
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
$
Approach 3 - working:
$ docker container exec db3 mariadb -u root -p'Pa$$word' -e 'SHOW DATABASES;'
Database
information_schema
mysql
performance_schema
sys
$
Approaches 4, 5 and 6 also work and the result is the same as approach 3.
Of course, this result is not completely inexplicable. To understand the reason, it is enough to look at the pipeline execution log.
Here is a sample for approach 1:
...
[Pipeline] { (Escape Test #1 - NOK)
[Pipeline] sh
+ docker container run -d --name db1 -e MYSQL_ROOT_PASSWORD=Pa6768word mariadb
...
And for approach 2:
...
[Pipeline] { (Escape Test #2 - NOK)
[Pipeline] sh
+ docker container run -d --name db2 -e MYSQL_ROOT_PASSWORD=Pa6948word mariadb
...
We can see that the two consecutive $$ symbols are replaced with a value corresponding to the number of the current process. That is, instead of the expected Pa$$word, we get something different - in one case Pa6768word, and in the other - Pa6948word. Of course, the next time the values will be different.
That’s it. We just have to be careful about how exactly we enclose the commands we’re going to execute and how we handle the special characters in them. :)