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:
- Using
subprocessdoesn't work on Heroku, asNo such file or directory: 'ping'is returned when trying it from the Heroku app. - There are many Python packages that execute a ping, but they all require root access, which isn't possible from Heroku.
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 <[email protected]>
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.
Install the ping command
My first suggestion is to install the
iputils-pingpackage 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.Command not found
Except in this case,
pingwon't be on thePATH, so simply runningpingwon't work.This is because of how the Apt buildpack operates. Instead of simply
apt-get installing everything, it pulls the.debfiles down and extracts them to a special location:$BUILD_DIR/.apt/. It then adds$HOME/.apt/usr/binto thePATHenvironment variable.For most binaries, this will work fine. Except the
iputlis-pingpackage (and the alternativeinetutils-pingpackage) put thepingbinary in/bin/, not/usr/bin/.On a regular Ubuntu machine,
/binis simply a symlink to/usr/binso either path will work, but the Apt buildpack doesn't have a link like that.pingis only available as$HOME/.apt/bin/ping, and the buildpack does not add$HOME/.apt/binto thePATH.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 modifyingPATHmay be the best solution.I think that should be achievable by adding a file like
.profile.d/000-ping.shto your repository¹:...except root
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.
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/binto.apt/usr/bin, mimicking a real system. I may submit a pull request if I can verify that this works.