Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows support #39

Merged
merged 3 commits into from
Jul 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion gigalixir/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -33,6 +35,7 @@
import json
import netrc
import os
import platform
from functools import wraps
import pkg_resources

Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion gigalixir/api_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
37 changes: 19 additions & 18 deletions gigalixir/netrc.py
Original file line number Diff line number Diff line change
@@ -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':
jesseshieh marked this conversation as resolved.
Show resolved Hide resolved
return "_netrc"
else:
return ".netrc"

def get_netrc_file():
jesseshieh marked this conversation as resolved.
Show resolved Hide resolved
# 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
Expand Down
5 changes: 5 additions & 0 deletions gigalixir/observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions gigalixir/openers/windows.py
Original file line number Diff line number Diff line change
@@ -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)
jesseshieh marked this conversation as resolved.
Show resolved Hide resolved

2 changes: 2 additions & 0 deletions gigalixir/routers/darwin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions gigalixir/routers/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 16 additions & 0 deletions gigalixir/routers/windows.py
Original file line number Diff line number Diff line change
@@ -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)
jesseshieh marked this conversation as resolved.
Show resolved Hide resolved

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
19 changes: 13 additions & 6 deletions test/gigalixir_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down