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

Add login shell mode #5

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,34 @@ envkernel lmod --name=NAME [envkernel options] [module ...]



## Login shell

This runs the kernel through a login shell, for example `bash -l
-c [kernel-cmd]`.

### Login shell example

```shell
envkernel loginshell --name=python3-clean
```

### Login shell arguments

```shell
envkernel loginshell --name=NAME [envkernel options] [login shell options]
```

* `--shell=SHELL`. Shell to invoke, default=`bash`. The shell has to
accept the `-l` (to amke it a login shell) and `-c` options (to
accept the kernel command to run).

Any other unknown argument will be passed through to the shell, so you
could, for example, use `--norc` for bash.





## Other kernels

Envkernel isn't specific to the IPython kernel. It defaults to
Expand Down
24 changes: 24 additions & 0 deletions envkernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,30 @@ def run(self):



class loginshell(envkernel):
def setup(self):
super().setup()
parser = argparse.ArgumentParser()
parser.add_argument('--shell', default="bash")
args, unknown_args = parser.parse_known_args(self.argv)

kernel = self.get_kernel()
original_argv = printargs(kernel['argv'])
kernel['argv'] = [
args.shell,
"-l",
*unknown_args,
"-c",
printargs(kernel['argv']),
]
if 'display_name' not in kernel:
kernel['display_name'] = "{} with login shell".format(
original_argv)
self.install_kernel(kernel, name=self.name, user=self.user,
replace=self.replace, prefix=self.prefix)



def main(argv=sys.argv):
mod = argv[1]
if mod in {'-h', '--help'}:
Expand Down
63 changes: 58 additions & 5 deletions test_envkernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,20 @@
"kernel_name": ""
}
"""
ALL_MODULES = ["conda", "virtualenv", "lmod", "docker", "singularity"]

# These modules re-invoke envkernel at runtime.
ALL_RECURSIVE_MODULES = [
"conda",
"virtualenv",
"lmod",
"docker",
"singularity",
]
# These modules may not re-invoke envkernel at runtime, so they need
# to be tested slightly differently.
ALL_MODULES = ALL_RECURSIVE_MODULES + [
"loginshell",
]


def install(d, argv, name='testkernel'):
Expand All @@ -38,15 +51,33 @@ def install(d, argv, name='testkernel'):
return get(d, name)

def get(d, name):
"""From an installed kernel, return dict with properties for testing."""
"""From an installed kernel, return dict with properties for testing.

kernel: raw decoded JSON object
kernel['args']: full kernel argv, split into ek and k below.
dir: the directory it was installed to
ek: envkernel options (used when invoking again at runtime)
k: kernel options

"""
dir_ = pjoin(d, 'share/jupyter/kernels/', name)
kernel = json.load(open(pjoin(dir_, 'kernel.json')))
return {
data = {
'dir': dir_,
'kernel': kernel,
'ek': envkernel.split_doubledash(kernel['argv'])[0],
'k': envkernel.split_doubledash(kernel['argv'])[1],
'k': envkernel.split_doubledash(kernel['argv'])[1] if '--' in kernel['argv'] else None,
}
# For loginshell, there is no -- in the argv, so we can't split
# between ek (envkernel args) and k (kernel args) Work around this
# by manually handling this case, and splitting on '-c'.
if '--' not in kernel['argv']:
if '-c' in kernel['argv']:
c_arg = data['ek'].index('-c')
# 'k' is joined into one argument loginshell
data['k'] = shlex.split(data['ek'][c_arg+1:][0])
data['ek'] = data['ek'][:c_arg+1]
return data

def run(d, kern, execvp=lambda _argv0, argv: 0):
"""Start envkernel in "run" mode to see if it can run successfully.
Expand Down Expand Up @@ -78,6 +109,12 @@ def replace_conn_file(arg, connection_file):
else:
return arg

def all_recursive_modes(modes=None):
"""All the different modes that re-invoke envkernel"""
if not modes:
modes = ALL_RECURSIVE_MODULES
return pytest.mark.parametrize("mode", modes)

def all_modes(modes=None):
if not modes:
modes = ALL_MODULES
Expand All @@ -91,7 +128,7 @@ def is_sublist(list_, sublist):



@all_modes()
@all_recursive_modes()
def test_basic(d, mode):
kern = install(d, "%s MOD1"%mode)
#assert kern['argv'][0] == 'envkernel' # defined above
Expand Down Expand Up @@ -272,6 +309,22 @@ def test_singularity(d):
assert kern['ek'][-1] == '/PATH/TO/IMAGE2'
assert '--some-arg=AAA' in kern['ek']

def test_loginshell(d):
kern = install(d, "loginshell --kernel-cmd=./KERNEL")
#assert kern['argv'][0] == 'envkernel' # defined above
print(kern)
assert kern['ek'][0] == 'bash'
assert '-l' in kern['ek']
assert '-c' in kern['ek']
c_arg = kern['kernel']['argv'].index('-c')
assert kern['kernel']['argv'][c_arg:c_arg+2] == ['-c', './KERNEL']

kern = install(d, "loginshell --shell=zsh")
assert kern['ek'][0] == 'zsh'

kern = install(d, "loginshell --norc")
assert '--norc' in kern['ek']



# Test running kernels
Expand Down