This post is about a minor contribution I did to SDKMAN!. Refer to this previous post for a quick overview of the tool and its features.
I've been using SDKMAN!, maintained by Marco Vermeulen, for a while now and closely following its evolution. Marco recently added the possibility of installing the Java SDK and I started adapting my Ansible scripts to configure my development environment (check out a previous blog post about it) to use SDKMAN! instead of installing Java through apt-get
.
During the process, which took some spare hours among several days, it suddenly stopped working.
I have an Ansible script which downloads and installs SDKMAN!, and runs a bash
script:
The sdkman.sh
script loads the sdk
command, forces an update, flushes the candidates and finally installs the SDKs I need, including the Java one:
Out of the blue, the Ansible script hanged indefinitely while executing sdk install java
.
I thought it was related to the echo "Y" |
I used to automatically accept the license while installing Java. Since Ansible output is only displayed when the command is finished, I began by piping the stdout
to a file so I could see what was going on while executing the bash
script. Then I could confirm that it stuck while executing sdk install java
.
Then I tried to execute the sdkman.sh
script directly without using Ansible, and it worked fine! I commented the sdk install XXX
commands and tried again using Ansible, while installing the SKDs directly from bash afterwards. It worked as expected.
I restored the original scripts and reviewed the Ansible documentation on how to run bash
scripts. I tried a couple of alternatives with the same result: it hanged. Finally I resorted to googling "bash stuck ansible" and came across this Ansible issue.
I reviewed it throughly but I couldn't find anything remotely related to what I was doing in my Ansible or bash
scripts.
I called it a day.
The day after I came to think: if I have changed nothing and it was working some days ago, what else could have changed? I opened the sdkman-cli repository, reviewed the latest commits and stumbled into something familiar in this one. I remembered recently seeing /dev/urandom
somewhere else, so I rerun the previous Google search, found again the Ansible issue and there it was!
According to this StackOverflow thread:
You should never use
cat
with/dev/urandom
. Nor should you use any utilities which are designed for text files.
/dev/urandom
is a continuous stream of random data. It will never produce an end of file. Buffered reads will fill the read buffer, so even if you are piping the output ofcat
into some other program, the read won't be terminated until the pipe is closed.
It turns out using cat
along with /dev/urandom
might cause bash scripts run from Ansible to get stuck.
Needless to say, I quickly applied the given solution in the Ansible issue: replacing cat /dev/urandom | ...
with ... < /dev/urandom
, but surprisingly it didn't work out. I decided to leave /dev/urandom
out of the equation in order to prove it was the cause, so I looked for another way to generate random strings.
I finally used a simple date +%s%N
including the nanoseconds to minimize the probability of a clash in concurrent executions. That allowed the Ansible script to execute properly without hanging, yay!
After a quick personal celebration (it took me around 4 hours to get there) I forked the sdkman-cli repository and made a pull request replacing the random string generation to be Ansible-friendly:
In the PR I specified that it might not be the best solution and that I had only been able to test it in Ubuntu, but since it also uses sha256sum
and base64
I wasn't sure if it'd work in other OSs.
Marco answered shortly after, confirming that neither of these are available by default on all Linux distributions, and also not on OSX and Cygwin.
The day after I came up with the intuition that maybe using just head
instead of cat
could be enough, since head
might ensure a finite sequence in contrast to cat
:
The test was successful, so I updated my PR.
In the meantime I updated the sdkman.sh
script to make that change, while waiting for the PR to be merged:
This has been the smallest contribution I've ever made, literally. I just replaced a cat
with a head
, nothing else. But it's also been the most gratifying because I don't have a solid grasp on shell scripting and I began without having any clue at all about what was going on.
After Marco tested it on OSX and Cygwin, he merged the PR and the fix became available in version 5.3.0
.
While sorting this issue out I became familiar with its source code, so I looked for any other improvement I could help with. These are the other contributions I've made to SDKMAN! so far:
Outdated command can update all outdated candidates
Besides listing the available candidate updates, the outdated
command (later renamed to upgrade
) allows to directly upgrade all candidates at once. This improvement was suggested by Albert Serrallé.
Make first installed candidate version automatically the default one
When installing a candidate for the first time you'll surely want to make it the default version. Instead of asking the user for confirmation, the first candidate version will be set to default automatically.
After improving the outdated
command, Marco suggested to rename it to upgrade
.
I built a completion file for zsh
so anyone can have command completion and description.
Would you like to leave a comment? Since this blog is hosted on GitHub Pages there's no straightforward way to do so.
Instead, you can add a comment in this GitHub issue. If you'd like to see it here, refresh this page after posting the comment.