diff --git a/setup.py b/setup.py index 59222b90..44b1c4d6 100644 --- a/setup.py +++ b/setup.py @@ -43,9 +43,11 @@ 'rocker.extensions': [ 'dev_helpers = rocker.extensions:DevHelpers', 'env = rocker.extensions:Environment', + 'git = rocker.git_extension:Git', + 'home = rocker.extensions:HomeDir', 'nvidia = rocker.nvidia_extension:Nvidia', 'pulse = rocker.extensions:PulseAudio', - 'home = rocker.extensions:HomeDir', + 'ssh = rocker.ssh_extension:Ssh', 'user = rocker.extensions:User', 'x11 = rocker.nvidia_extension:X11', ] diff --git a/src/rocker/git_extension.py b/src/rocker/git_extension.py new file mode 100644 index 00000000..6bb4dbe2 --- /dev/null +++ b/src/rocker/git_extension.py @@ -0,0 +1,57 @@ +# Copyright 2019 Open Source Robotics Foundation + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from argparse import ArgumentTypeError +import os +from rocker.extensions import RockerExtension + + +class Git(RockerExtension): + + name = 'git' + + @classmethod + def get_name(cls): + return cls.name + + def precondition_environment(self, cli_args): + pass + + def validate_environment(self, cli_args): + pass + + def get_preamble(self, cli_args): + return '' + + def get_snippet(self, cli_args): + return '' + + def get_docker_args(self, cli_args): + args = '' + system_gitconfig = '/etc/gitconfig' + user_gitconfig = os.path.expanduser('~/.gitconfig') + user_gitconfig_target = '/root/.gitconfig' + if 'user' in cli_args and cli_args['user']: + user_gitconfig_target = user_gitconfig + if os.path.exists(system_gitconfig): + args += ' -v {system_gitconfig}:{system_gitconfig}:ro'.format(**locals()) + if os.path.exists(user_gitconfig): + args += ' -v {user_gitconfig}:{user_gitconfig_target}:ro'.format(**locals()) + return args + + @staticmethod + def register_arguments(parser): + parser.add_argument('--git', + action='store_true', + help="Use the global Git settings from the host (/etc/gitconfig and ~/.gitconfig)") diff --git a/src/rocker/ssh_extension.py b/src/rocker/ssh_extension.py new file mode 100644 index 00000000..34303cc5 --- /dev/null +++ b/src/rocker/ssh_extension.py @@ -0,0 +1,52 @@ +# Copyright 2019 Open Source Robotics Foundation + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from argparse import ArgumentTypeError +import os +import shlex + +from rocker.extensions import RockerExtension + + +class Ssh(RockerExtension): + + name = 'ssh' + + @classmethod + def get_name(cls): + return cls.name + + def precondition_environment(self, cli_args): + pass + + def validate_environment(self, cli_args): + pass + + def get_preamble(self, cli_args): + return '' + + def get_snippet(self, cli_args): + return '' + + def get_docker_args(self, cli_args): + args = '' + if 'SSH_AUTH_SOCK' in os.environ: + args += ' -e SSH_AUTH_SOCK -v ' + shlex.quote('{SSH_AUTH_SOCK}:{SSH_AUTH_SOCK}'.format(**os.environ)) + return args + + @staticmethod + def register_arguments(parser): + parser.add_argument('--ssh', + action='store_true', + help="Forward SSH agent into the container") diff --git a/test/test_git_extension.py b/test/test_git_extension.py new file mode 100644 index 00000000..ccbdff82 --- /dev/null +++ b/test/test_git_extension.py @@ -0,0 +1,79 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import argparse +import em +import getpass +import os +import unittest +from pathlib import Path +import pwd + + +from rocker.cli import list_plugins +from rocker.extensions import name_to_argument + +from test_extension import plugin_load_parser_correctly + +class ExtensionsTest(unittest.TestCase): + def test_name_to_argument(self): + self.assertEqual(name_to_argument('asdf'), '--asdf') + self.assertEqual(name_to_argument('as_df'), '--as-df') + self.assertEqual(name_to_argument('as-df'), '--as-df') + + +class GitExtensionTest(unittest.TestCase): + + def setUp(self): + # Work around interference between empy Interpreter + # stdout proxy and test runner. empy installs a proxy on stdout + # to be able to capture the information. + # And the test runner creates a new stdout object for each test. + # This breaks empy as it assumes that the proxy has persistent + # between instances of the Interpreter class + # empy will error with the exception + # "em.Error: interpreter stdout proxy lost" + em.Interpreter._wasProxyInstalled = False + + def test_git_extension(self): + plugins = list_plugins() + git_plugin = plugins['git'] + self.assertEqual(git_plugin.get_name(), 'git') + + p = git_plugin() + self.assertTrue(plugin_load_parser_correctly(git_plugin)) + + + mock_cliargs = {} + self.assertEqual(p.get_snippet(mock_cliargs), '') + self.assertEqual(p.get_preamble(mock_cliargs), '') + args = p.get_docker_args(mock_cliargs) + # self.assertFalse(args) + system_gitconfig = '/etc/gitconfig' + user_gitconfig = os.path.expanduser('~/.gitconfig') + user_gitconfig_target = '/root/.gitconfig' + if os.path.exists(system_gitconfig): + # TODO(tfoote) This isn't exercised on most systems, it would need to be mocked + self.assertIn('-v %s:%s' % (system_gitconfig, system_gitconfig), args) + if os.path.exists(user_gitconfig): + self.assertIn('-v %s:%s' % (user_gitconfig, user_gitconfig_target), args) + + # Test with user "enabled" + mock_cliargs = {'user': True} + user_args = p.get_docker_args(mock_cliargs) + if os.path.exists(user_gitconfig): + self.assertIn('-v %s:%s' % (user_gitconfig, user_gitconfig), user_args) diff --git a/test/test_ssh_extension.py b/test/test_ssh_extension.py new file mode 100644 index 00000000..692bf0a4 --- /dev/null +++ b/test/test_ssh_extension.py @@ -0,0 +1,73 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import argparse +import em +import getpass +import os +import unittest +from pathlib import Path +import pwd +import shlex + + +from rocker.cli import list_plugins +from rocker.extensions import name_to_argument + +from test_extension import plugin_load_parser_correctly + +class ExtensionsTest(unittest.TestCase): + def test_name_to_argument(self): + self.assertEqual(name_to_argument('asdf'), '--asdf') + self.assertEqual(name_to_argument('as_df'), '--as-df') + self.assertEqual(name_to_argument('as-df'), '--as-df') + + +class sshExtensionTest(unittest.TestCase): + + def setUp(self): + # Work around interference between empy Interpreter + # stdout proxy and test runner. empy installs a proxy on stdout + # to be able to capture the information. + # And the test runner creates a new stdout object for each test. + # This breaks empy as it assumes that the proxy has persistent + # between instances of the Interpreter class + # empy will error with the exception + # "em.Error: interpreter stdout proxy lost" + em.Interpreter._wasProxyInstalled = False + + def test_ssh_extension(self): + plugins = list_plugins() + ssh_plugin = plugins['ssh'] + self.assertEqual(ssh_plugin.get_name(), 'ssh') + + p = ssh_plugin() + self.assertTrue(plugin_load_parser_correctly(ssh_plugin)) + + + mock_cliargs = {} + self.assertEqual(p.get_snippet(mock_cliargs), '') + self.assertEqual(p.get_preamble(mock_cliargs), '') + # with SSH_AUTH_SOCK set + os.environ['SSH_AUTH_SOCK'] = 'foo' + args = p.get_docker_args(mock_cliargs) + self.assertIn('-e SSH_AUTH_SOCK -v ' + shlex.quote('{SSH_AUTH_SOCK}:{SSH_AUTH_SOCK}'.format(**os.environ)), args) + + #without it set + del os.environ['SSH_AUTH_SOCK'] + args = p.get_docker_args(mock_cliargs) + self.assertNotIn('SSH_AUTH_SOCK', args) \ No newline at end of file