diff --git a/gigalixir/__init__.py b/gigalixir/__init__.py index 71397db..7de56d7 100644 --- a/gigalixir/__init__.py +++ b/gigalixir/__init__.py @@ -1,8 +1,10 @@ from .shell import cast, call from .routers.linux import LinuxRouter from .routers.darwin import DarwinRouter +from .routers.windows import WindowsRouter from .openers.linux import LinuxOpener from .openers.darwin import DarwinOpener +from .openers.windows import WindowsOpener from . import observer as gigalixir_observer from . import user as gigalixir_user from . import app as gigalixir_app @@ -33,6 +35,7 @@ import json import netrc import os +import platform from functools import wraps import pkg_resources @@ -220,13 +223,20 @@ def cli(ctx, env): ctx.obj['host'] = host - PLATFORM = call("uname -s").lower() # linux or darwin + PLATFORM = platform.system().lower() # linux, darwin, or windows if PLATFORM == "linux": ctx.obj['router'] = LinuxRouter() ctx.obj['opener'] = LinuxOpener() elif PLATFORM == "darwin": ctx.obj['router'] = DarwinRouter() ctx.obj['opener'] = DarwinOpener() + elif PLATFORM == "windows": + try: + os.environ['HOME'] + except KeyError: + os.environ['HOME'] = os.environ['USERPROFILE'] + ctx.obj['router'] = WindowsRouter() + ctx.obj['opener'] = WindowsOpener() else: raise Exception("Unknown platform: %s" % PLATFORM) diff --git a/gigalixir/api_key.py b/gigalixir/api_key.py index fd9cc99..b339fc8 100644 --- a/gigalixir/api_key.py +++ b/gigalixir/api_key.py @@ -13,7 +13,7 @@ def regenerate(host, email, password, yes): raise Exception(r.text) else: key = json.loads(r.text)["data"]["key"] - if yes or click.confirm('Would you like to save your api key to your ~/.netrc file?'): + if yes or click.confirm('Would you like to save your api key to your ~/%s file?' % netrc.netrc_name()): netrc.update_netrc(email, key) else: logging.getLogger("gigalixir-cli").info('Your api key is %s' % key) diff --git a/gigalixir/netrc.py b/gigalixir/netrc.py index 40f22be..cb2f32b 100644 --- a/gigalixir/netrc.py +++ b/gigalixir/netrc.py @@ -1,41 +1,42 @@ from __future__ import absolute_import import netrc import os +import platform -def clear_netrc(): +def netrc_name(): + if platform.system().lower() == 'windows': + return "_netrc" + else: + return ".netrc" + +def get_netrc_file(): # TODO: support netrc files in locations other than ~/.netrc + fname = os.path.join(os.environ['HOME'], netrc_name()) try: - netrc_file = netrc.netrc() + netrc_file = netrc.netrc(fname) except IOError: # if netrc does not exist, touch it # from: http://stackoverflow.com/questions/1158076/implement-touch-using-python - fname = os.path.join(os.environ['HOME'], ".netrc") with open(fname, 'a'): os.utime(fname, None) - netrc_file = netrc.netrc() + netrc_file = netrc.netrc(fname) + + return netrc_file, fname + +def clear_netrc(): + netrc_file, fname = get_netrc_file() del netrc_file.hosts['git.gigalixir.com'] del netrc_file.hosts['api.gigalixir.com'] - file = os.path.join(os.environ['HOME'], ".netrc") - with open(file, 'w') as fp: + with open(fname, 'w') as fp: fp.write(netrc_repr(netrc_file)) def update_netrc(email, key): - # TODO: support netrc files in locations other than ~/.netrc - try: - netrc_file = netrc.netrc() - except IOError: - # if netrc does not exist, touch it - # from: http://stackoverflow.com/questions/1158076/implement-touch-using-python - fname = os.path.join(os.environ['HOME'], ".netrc") - with open(fname, 'a'): - os.utime(fname, None) - netrc_file = netrc.netrc() + netrc_file, fname = get_netrc_file() netrc_file.hosts['git.gigalixir.com'] = (email, None, key) netrc_file.hosts['api.gigalixir.com'] = (email, None, key) - file = os.path.join(os.environ['HOME'], ".netrc") - with open(file, 'w') as fp: + with open(fname, 'w') as fp: fp.write(netrc_repr(netrc_file)) # Copied from https://github.com/enthought/Python-2.7.3/blob/master/Lib/netrc.py#L105 diff --git a/gigalixir/observer.py b/gigalixir/observer.py index dcae417..81a98b1 100644 --- a/gigalixir/observer.py +++ b/gigalixir/observer.py @@ -14,6 +14,9 @@ from six.moves.urllib.parse import quote def observer(ctx, app_name, erlang_cookie=None, ssh_opts=""): + if not ctx.obj['router'].supports_multiplexing(): + raise Exception("The observer command is not supported on this platform.") + host = ctx.obj['host'] r = requests.get('%s/api/apps/%s/observer-commands' % (host, quote(app_name.encode('utf-8'))), headers = { 'Content-Type': 'application/json', @@ -134,6 +137,8 @@ def ensure_port_free(port): # if the port is in use, then a pid is found, this "succeeds" and continues # if the port is free, then a pid is not found, this "fails" and raises a CalledProcessError pid = call("lsof -wni tcp:%(port)s -t" % {"port": port}) + # If multiplexing gets supported later, on Windows this command would be: + # pid = call("netstat -p tcp -n | find \"\"\":%(port)s\"\"\" % {"port": port}) raise Exception("It looks like process %s is using port %s on your local machine. We need this port to be able to connect observer. Please kill this process on your local machine and try again. e.g. `kill %s`" % (pid, port, pid)) except subprocess.CalledProcessError: # success! continue diff --git a/gigalixir/openers/windows.py b/gigalixir/openers/windows.py new file mode 100644 index 0000000..a49bfae --- /dev/null +++ b/gigalixir/openers/windows.py @@ -0,0 +1,8 @@ +from gigalixir.shell import cast +import logging + +class WindowsOpener(object): + def open(self, url): + logging.getLogger("gigalixir-cli").info("Running: start %s" % url) + cast("start %s" % url) + diff --git a/gigalixir/routers/darwin.py b/gigalixir/routers/darwin.py index 7591323..783ba37 100644 --- a/gigalixir/routers/darwin.py +++ b/gigalixir/routers/darwin.py @@ -27,3 +27,5 @@ def unroute_to_localhost(self, ip): cast("sudo ifconfig lo0 %s netmask 255.255.255.255 -alias" % ip) subprocess.call("sudo pfctl -ef /etc/pf.conf".split()) + def supports_multiplexing(): + return True diff --git a/gigalixir/routers/linux.py b/gigalixir/routers/linux.py index 10bfdec..13ca02b 100644 --- a/gigalixir/routers/linux.py +++ b/gigalixir/routers/linux.py @@ -13,3 +13,6 @@ def unroute_to_localhost(self, ip): logging.getLogger("gigalixir-cli").info("If prompted, please enter your sudo password:") cast("sudo iptables -t nat -D OUTPUT -p all -d %(ip)s -j DNAT --to-destination 127.0.0.1" % {"ip": ip}) # cast("sudo iptables -t nat -L OUTPUT") + + def supports_multiplexing(): + return True \ No newline at end of file diff --git a/gigalixir/routers/windows.py b/gigalixir/routers/windows.py new file mode 100644 index 0000000..5e8b1fb --- /dev/null +++ b/gigalixir/routers/windows.py @@ -0,0 +1,16 @@ +from gigalixir.shell import cast +import logging + +class WindowsRouter(object): + def route_to_localhost(self, ip, epmd_port, distribution_port): + # NOTE: Untested as multiplexing not supported on platform + logging.getLogger("gigalixir-cli").info("Setting up route") + cast("route ADD %s MASK 255.255.255.255 127.0.0.1" % ip) + + def unroute_to_localhost(self, ip): + # NOTE: Untested as multiplexing not supported on platform + logging.getLogger("gigalixir-cli").info("Cleaning up route") + cast("route delete %s" % ip) + + def supports_multiplexing(self): + return False \ No newline at end of file diff --git a/test/gigalixir_test.py b/test/gigalixir_test.py index ebbe626..8b9e449 100644 --- a/test/gigalixir_test.py +++ b/test/gigalixir_test.py @@ -6,6 +6,13 @@ import click from click.testing import CliRunner import httpretty +import platform + +def netrc_name(): + if platform.system().lower() == 'windows': + return "_netrc" + else: + return ".netrc" @httpretty.activate def test_create_user(): @@ -23,7 +30,7 @@ def test_logout(): # Make sure this test does not modify the user's netrc file. with runner.isolated_filesystem(): os.environ['HOME'] = '.' - with open('.netrc', 'w') as f: + with open(netrc_name(), 'w') as f: f.write("""machine api.gigalixir.com \tlogin foo@gigalixir.com \tpassword fake-api-key @@ -34,11 +41,11 @@ def test_logout(): \tlogin foo \tpassword fake-password """) - os.chmod(".netrc", 0o600) + os.chmod(netrc_name(), 0o600) result = runner.invoke(gigalixir.cli, ['logout']) assert result.output == '' assert result.exit_code == 0 - with open('.netrc') as f: + with open(netrc_name()) as f: assert f.read() == """machine github.com \tlogin foo \tpassword fake-password @@ -54,7 +61,7 @@ def test_login(): os.environ['HOME'] = '.' result = runner.invoke(gigalixir.cli, ['login', '--email=foo@gigalixir.com'], input="password\ny\n") assert result.exit_code == 0 - with open('.netrc') as f: + with open(netrc_name()) as f: assert f.read() == """machine api.gigalixir.com \tlogin foo@gigalixir.com \tpassword fake-api-key @@ -75,7 +82,7 @@ def test_login_escaping(): os.environ['HOME'] = '.' result = runner.invoke(gigalixir.cli, ['login', '--email=foo@gigalixir.com'], input="p:assword\ny\n") assert result.exit_code == 0 - with open('.netrc') as f: + with open(netrc_name()) as f: assert f.read() == """machine api.gigalixir.com \tlogin foo@gigalixir.com \tpassword fake-api-key @@ -274,7 +281,7 @@ def test_create_api_key(): os.environ['HOME'] = '.' result = runner.invoke(gigalixir.cli, ['reset_api_key', '--email=foo@gigalixir.com'], input="password\ny\n") assert result.exit_code == 0 - with open('.netrc') as f: + with open(netrc_name()) as f: assert f.read() == """machine api.gigalixir.com \tlogin foo@gigalixir.com \tpassword another-fake-api-key