Contributing to Open Source Projects II - SDKMAN!

Posted on February 14, 2017 · 6 min read

Context

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.

The story

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.

The issue

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:

https://gist.github.com/pbassiner/d9df5e60477a4d1b0d8ef354e9a9f6ea#file-ansible_sdkman-yml

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:

https://gist.github.com/pbassiner/d9df5e60477a4d1b0d8ef354e9a9f6ea#file-sdkman-sh

Out of the blue, the Ansible script hanged indefinitely while executing sdk install java.

Troubleshooting

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.

Starting over

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 of cat 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!

Proposing a solution

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:

https://gist.github.com/pbassiner/d9df5e60477a4d1b0d8ef354e9a9f6ea#file-random_from_date-sh

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.

Improving the solution

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:

https://gist.github.com/pbassiner/d9df5e60477a4d1b0d8ef354e9a9f6ea#file-urandom_with_head-sh

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:

https://gist.github.com/pbassiner/d9df5e60477a4d1b0d8ef354e9a9f6ea#file-sdkman_urandom_hack-sh

In conclusion

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.

Update

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:


Comments

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.