Run shell script inside ssh session inside Jenkinsfile - shell

I'm trying to run a complete script while the ssh session is live instead of single commands.
Here is my current code:
sh "ssh -tt -o StrictHostKeyChecking=no ubuntu#IPV4_DNS uptime"
sh "ssh -v ubuntu#IPV4_DNS docker pull X:${BUILD_NUMBER}"
sh "ssh -v ubuntu#IPV4_DNS docker rm -f test"
sh "ssh -v ubuntu#IPV4_DNS docker run --name=test -d -p 3000:3000X:${BUILD_NUMBER}"
The desired code is something like this, but the following doesn't work:*
sh "ssh -tt -o StrictHostKeyChecking=no ubuntu#IPV4_DNS uptime"
sh ''' ssh -v ubuntu#IPV4_DNS docker pull X:${BUILD_NUMBER}
&& docker rm -f test && docker run --name=test -d -p 3000:3000X:${BUILD_NUMBER}
'''

ssh something here && something else && another one
runs something here in the ssh session, and something else and another one locally. You want to add quotes to pass the entire command line to ssh.
sh "ssh -tt -o StrictHostKeyChecking=no ubuntu#IPV4_DNS uptime"
sh """ssh -v ubuntu#IPV4_DNS 'docker pull X:${BUILD_NUMBER} &&
docker rm -f test &&
docker run --name=test -d -p "3000:3000X:${BUILD_NUMBER}"'
"""
I switched to triple double quotes instead of triple single quotes, assuming you want Jenkins to expand ${BUILD_NUMBER} for you.
The original question asked about Bash, but for the record, you are running sh here, not Bash. If you wanted to use Bash features in a Jenkinsfile, you can add a shebang #!/usr/bin/env bash or similar as the very first line of the command. But that's not necessary here; all these commands are simple and completely POSIX. (Maybe see also Difference between sh and bash)

Related

SSH and Docker Exec together are not accepting multiple commands

I need to create a shell script that connects to a remote machine using SSH and then runs some commands inside a docker container that is running on that machine.
I want the below command to work. But it only executes first command in the container.
ssh -i key user#1.1.1.1 docker exec my-container bash -c command1 && command2 && command3
So far the best solution I could come up with is this:
ssh -i key user#1.1.1.1 "docker exec my-container bash -c 'command1 && command2 && command3'"
But it only works with some commands. I can run commands like mkdir echo but I couldn't use curl with it.
ssh -i key user#1.1.1.1 "docker exec my-container bash -c 'curl --verbose --stderr stderr -X GET "http://2.2.2.2:5000/file/download" -H "Authorization: Bearer $1" > curl_out
I somehow need to make the curl command work. It succesfully expands $1 as the authorization token but curl command does not see use the headers. I couldn't get it to work.
Is there a better way of constructing this kind of nested command pipe. I have tried like 50 different combinations of quotes, different variables, trying to write the echo inside a shell script inside the container and then running it. Each solution fails upon trying to use complex commands with multiple options / arguments.
When you write this:
ssh -i key user#1.1.1.1 docker exec my-container \
bash -c command1 && command2 && command3
You are just creating a local shell pipeline. That's the same thing as if you were to run:
date && command2 && command3
Your shell doesn't magically know that you intended to run those second two commands on the remote host. If you want to pass that entire shell pipeline to the remote host, you need to quote it.
You might be tempted to do something like this:
ssh -i key user#1.1.1.1 docker exec my-container \
bash -c "command1 && command2 && command3"
But that still won't work as intended: in this case, you are running the command docker exec my-container bash -c command1 && command2 && command3 on the remote host. That is, only command1 is being run inside the container. You need another level of quoting:
ssh -i key user#1.1.1.1 docker exec my-container \
'bash -c "command1 && command2 && command3"'

How not to terminate after carried out commands in bash

After carrying out commands with "-c" option of bash, how can I make the terminal wait for input while preserving the environment?
Like CMD /K *** or pwsh -NoExit -Command ***.
From a comment by Cyrus:
You can achieve something similar by abusing the --rcfile option:
bash --rcfile <(echo "export PS1='> ' && ls")
From bash manpage:
--rcfile file
Execute commands from file instead of the system wide initialization file /etc/bash.bashrc and the standard personal initialization file ~/.bashrc if the shell is interactive
This is the answer I was looking for. Thank you!!
As an example of use, I use the following method to use the latest docker image with my preferred repository without building the image:
# Call bash in the container from bash
docker run --rm -it ubuntu:22.04 bash -c "bash --rcfile <(echo 'sed -i -E '\''s%^(deb(-src|)\s+)https?://(archive|security)\.ubuntu\.com/ubuntu/%\1http://mirrors.xtom.com/ubuntu/%'\'' /etc/apt/sources.list && apt update && FooBar=`date -uIs`')"
# ... from pwsh
docker run --rm -it ubuntu:22.04 bash -c "bash --rcfile <(echo 'sed -i -E '\''s%^(deb(-src|)\s+)https?://(archive|security)\.ubuntu\.com/ubuntu/%\1http://mirrors.xtom.com/ubuntu/%'\'' /etc/apt/sources.list && apt update && FooBar=``date -uIs``')"
# Call dash (BusyBox ash) in the container from bash
docker run --rm -it alpine:latest ash -c "ash -c 'export ENV=\$1;ash' -s <(echo 'sed -i -E '\''s%^https?://dl-cdn\.alpinelinux\.org/alpine/%https://ftp.udx.icscoe.jp/Linux/alpine/%'\'' /etc/apk/repositories && apk update && FooBar=`date -uIs`')"
# ... from pwsh
docker run --rm -it alpine:latest ash -c "ash -c 'export ENV=`$1;ash' -s <(echo 'sed -i -E '\''s%^https?://dl-cdn\.alpinelinux\.org/alpine/%https://ftp.udx.icscoe.jp/Linux/alpine/%'\'' /etc/apk/repositories && apk update && FooBar=``date -uIs``')"

Bash Script fails with error: OCI runtime exec failed

I am running the below script and getting error.
#!/bin/bash
webproxy=$(sudo docker ps -a --format "{{.Names}}"|grep webproxy)
webproxycheck="curl -k -s https://localhost:\${nginx_https_port}/HealthCheckService"
if [ -n "$webproxy" ] ; then
sudo docker exec $webproxy sh -c "$webproxycheck"
fi
Here is my docker ps -a output
$sudo docker ps -a --format "{{.Names}}"|grep webproxy
webproxy-dev-01
webproxy-dev2-01
when i run the command individually it works. For Example:
$sudo docker exec webproxy-dev-01 sh -c 'curl -k -s https://localhost:${nginx_https_port}/HealthCheckService'
HEALTHCHECK_OK
$sudo docker exec webproxy-dev2-01 sh -c 'curl -k -s https://localhost:${nginx_https_port}/HealthCheckService'
HEALTHCHECK_OK
Here is the error i get.
$ sh healthcheck.sh
OCI runtime exec failed: exec failed: container_linux.go:348: starting container process caused "exec: \"webproxy-dev-01\": executable file not found in $PATH": unknown
Could someone please help me with the error. Any help will be greatly appreciated.
Because the variable contains two tokens (on two separate lines) that's what the variable expands to. You are running
sudo docker exec webproxy-dev-01 webproxy-dev2-01 ...
which of course is an error.
It's not clear what you actually expect to happen, but if you want to loop over those values, that's
for host in $webproxy; do
sudo docker exec "$host" sh -c "$webproxycheck"
done
which will conveniently loop zero times if the variable is empty.
If you just want one value, maybe add head -n 1 to the pipe, or pass a more specific regular expression to grep so it only matches one container. (If you have control over these containers, probably run them with --name so you can unambiguously identify them.)
Based on your given script, you are trying to "exec" the following
sudo docker exec webproxy-dev2-01
webproxy-dev-01 sh -c "curl -k -s https://localhost:${nginx_https_port}/HealthCheckService"
As you see, here is your error.
sudo docker exec webproxy-dev2-01
webproxy-dev-01 [...]
The problem is this line:
webproxy=$(sudo docker ps -a --format "{{.Names}}"|grep webproxy)
which results in the following (you also posted this):
webproxy-dev2-01
webproxy-dev-01
Now, the issue is, that your docker exec command now takes both images names (coming from the variable assignment $webproxy), interpreting the second entry (which is webproxy-dev-01 and sepetrated by \n) as the exec command. This is now intperreted as the given command which is not valid and cannot been found: That's what the error tells you.
A workaround would be the following:
webproxy=$(sudo docker ps -a --format "{{.Names}}"| grep webproxy | head -n 1)
It only graps the first entry of your output. You can of course adapt this to do this in a loop.
A small snippet:
#!/bin/bash
webproxy=$(sudo docker ps -a --format "{{.Names}}"| grep webproxy )
echo ${webproxy}
webproxycheck="curl -k -s https://localhost:\${nginx_https_port}/HealthCheckService"
while IFS= read -r line; do
if [ -n "$line" ] ; then
echo "sudo docker exec ${line} sh -c \"${webproxycheck}\""
fi
done <<< "$webproxy"

Read variables in nested quotes

I want to ssh into a host and start a container and run some commands. So the code will be like this:
ssh $host 'screen -L -d -m bash -c "docker run "\
"--network=host -v ~/data:/data myimage:${TAG_NAME}"\
" /bin/bash -c \" some command.... \""'
The question is simple, since I was using single quote, I can't read the ${TAG_NAME}. Is there any way to write this kind of nested quotes and also pass the variable?
You can stop and start your single quotes to include the environment variable, like so:
echo 'foo'"$HOME"'foo'
For your example, the way to include an env var (from your local system) in the command that runs on $host would be:
ssh $host 'screen -L -d -m bash -c "docker run'\
' --network=host -v ~/data:/data myimage:'"$TAG_NAME"\
' /bin/bash -c \" some command.... \""'

docker run -i -t image /bin/bash - source files first

This works:
# echo 1 and exit:
$ docker run -i -t image /bin/bash -c "echo 1"
1
# exit
# echo 1 and return shell in docker container:
$ docker run -i -t image /bin/bash -c "echo 1; /bin/bash"
1
root#4c064f2554de:/#
Question: How could I source a file into the shell? (this does not work)
$ docker run -i -t image /bin/bash -c "source <(curl -Ls git.io/apeepg) && /bin/bash"
# content from http://git.io/apeepg is sourced and shell is returned
root#4c064f2554de:/#
In my case, I use RUN source command (which will run using /bin/bash) in a Dockerfile to install nvm for node.js
Here is an example.
FROM ubuntu:14.04
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
...
...
RUN source ~/.nvm/nvm.sh && nvm install 0.11.14
I wanted something similar, and expanding a bit on your idea, came up with the following:
docker run -ti --rm ubuntu \
bash -c 'exec /bin/bash --rcfile /dev/fd/1001 \
1002<&0 \
<<<$(echo PS1=it_worked: ) \
1001<&0 \
0<&1002'
--rcfile /dev/fd/1001 will use that file descriptor's contents instead of .bashrc
1002<&0 saves stdin
<<<$(echo PS1=it_worked: ) puts PS1=it_worked: on stdin
1001<&0 moves this stdin to fd 1001, which we use as rcfile
0<&1002 restores the stdin that we saved initially
You can use .bashrc in interactive containers:
RUN curl -O git.io/apeepg.sh && \
echo 'source apeepg.sh' >> ~/.bashrc
Then just run as usual with docker run -it --rm some/image bash.
Note that this will only work with interactive containers.
I don't think you can do this, at least not right now. What you could do is modify your image, and add the file you want to source, like so:
FROM image
ADD my-file /my-file
RUN ["source", "/my-file", "&&", "/bin/bash"]

Resources