I have a Django app running on Heroku. I want to ping an external website to see if it responds from my app. I have found multiple solutions locally, but none work on Heroku:
subprocess
doesn't work on Heroku, as No such file or directory: 'ping'
is returned when trying it from the Heroku app.I installed iputils-ping
via the https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku-community/apt.tgz
buildpack, and confirmed I see it installed in the deploy logs:
remote: -----> Installing iputils-ping_3%3a20190709-3ubuntu1_amd64.deb
remote: new Debian package, version 2.0.
remote: size 40000 bytes: control archive=1204 bytes.
remote: 648 bytes, 18 lines control
remote: 334 bytes, 5 lines md5sums
remote: 693 bytes, 32 lines * postinst #!/bin/sh
remote: Package: iputils-ping
remote: Source: iputils
remote: Version: 3:20190709-3ubuntu1
remote: Architecture: amd64
remote: Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
remote: Installed-Size: 106
I have an Aptfile inside my root folder:
In [4]: os.listdir()
Out[4]:
['Aptfile',...]
and in that is one line:
iputils-ping
.
I then try to run: subprocess.run(["ping", "-c", "2", url], stdout=subprocess.PIPE,stderr=subprocess.PIPE, text=True)
but I get an error:
<ipython-input-5-f67d5e6c83d7> in <module>
----> 1 subprocess.run(["ping", "-c", "2", url], stdout=subprocess.PIPE,stderr=subprocess.PIPE, text=True)
~/.heroku/python/lib/python3.9/subprocess.py in run(input, capture_output, timeout, check, *popenargs, **kwargs)
499 kwargs['stderr'] = PIPE
500
--> 501 with Popen(*popenargs, **kwargs) as process:
502 try:
503 stdout, stderr = process.communicate(input, timeout=timeout)
~/.heroku/python/lib/python3.9/subprocess.py in __init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask)
945 encoding=encoding, errors=errors)
946
--> 947 self._execute_child(args, executable, preexec_fn, close_fds,
948 pass_fds, cwd, env,
949 startupinfo, creationflags, shell,
~/.heroku/python/lib/python3.9/subprocess.py in _execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session)
1817 if errno_num != 0:
1818 err_msg = os.strerror(errno_num)
-> 1819 raise child_exception_type(errno_num, err_msg, err_filename)
1820 raise child_exception_type(err_msg)
1821
FileNotFoundError: [Errno 2] No such file or directory: 'ping'
This is more complex than it might appear on the surface. Unfortunately, it looks like it may not be possible.
My first suggestion is to install the iputils-ping
package using the Apt buildpack and call that from your Python code. This is a good approach for installing Ubuntu packages on your dyno, and it usually works.
Except in this case, ping
won't be on the PATH
, so simply running ping
won't work.
This is because of how the Apt buildpack operates. Instead of simply apt-get install
ing everything, it pulls the .deb
files down and extracts them to a special location: $BUILD_DIR/.apt/
. It then adds $HOME/.apt/usr/bin
to the PATH
environment variable.
For most binaries, this will work fine. Except the iputlis-ping
package (and the alternative inetutils-ping
package) put the ping
binary in /bin/
, not /usr/bin/
.
On a regular Ubuntu machine, /bin
is simply a symlink to /usr/bin
so either path will work, but the Apt buildpack doesn't have a link like that. ping
is only available as $HOME/.apt/bin/ping
, and the buildpack does not add $HOME/.apt/bin
to the PATH
.
So, you should be able to run it via that full path (/app/.apt/bin/ping
). That's not ideal since it won't work locally, so modifying PATH
may be the best solution.
I think that should be achievable by adding a file like .profile.d/000-ping.sh
to your repository¹:
export PATH=/app/.apt/bin:$PATH
But even after jumping through the hoops of installing the package and finding the binary, it doesn't look like it will work. Dynos appear to have their networking sufficiently locked down so as to prevent outgoing pings.
/app/.apt/bin/ping: socket: Operation not permitted
Given that, it's unlikely that you'll be able to send a ping no matter what approach you try.
¹A better solution would be to update the buildpack to include a symlink from .apt/bin
to .apt/usr/bin
, mimicking a real system. I may submit a pull request if I can verify that this works.