def fg(client, project: Project, container_name: str, exec_object: Union[Command, Service], arguments: List[str], command_group: Optional[str]) -> int: # TODO: Piping | < # TODO: Not only /src into container but everything # Check if image exists try: image = client.images.get(exec_object["image"]) image_config = client.api.inspect_image(exec_object["image"])["Config"] except NotFound: print("Riptide: Pulling image... Your command will be run after that.", file=sys.stderr) try: client.api.pull(exec_object['image'] if ":" in exec_object['image'] else exec_object['image'] + ":latest") image = client.images.get(exec_object["image"]) image_config = client.api.inspect_image(exec_object["image"])["Config"] except ImageNotFound as ex: print("Riptide: Could not pull. The image was not found. Your command will not run :(", file=sys.stderr) return except APIError as ex: print("Riptide: There was an error pulling the image. Your command will not run :(", file=sys.stderr) print(' ' + str(ex), file=sys.stderr) return command = image_config["Cmd"] if "command" in exec_object: if isinstance(exec_object, Service): command = exec_object.get_command(command_group) else: command = exec_object["command"] builder = ContainerBuilder(exec_object["image"], command) builder.set_workdir(CONTAINER_SRC_PATH + "/" + get_current_relative_src_path(project)) builder.set_name(container_name) builder.set_network(get_network_name(project["name"])) builder.set_env(EENV_NO_STDOUT_REDIRECT, "yes") builder.set_args(arguments) if isinstance(exec_object, Service): builder.init_from_service(exec_object, image_config) builder.service_add_main_port(exec_object) else: builder.init_from_command(exec_object, image_config) builder.set_env(EENV_RUN_MAIN_CMD_AS_USER, "yes") builder.set_env(EENV_USER, str(getuid())) builder.set_env(EENV_GROUP, str(getgid())) # Using a new thread: # Add the container link networks after docker run started... I tried a combo of Docker API create and Docker CLI # start to make it cleaner, but 'docker start' does not work well for interactive commands at all, # so that's the best we can do AddNetLinks(container_name, client, project["links"]).start() return _spawn(builder.build_docker_cli())
def os_group(self) -> str: """ Returns the id of the current user's primary group as string (or 0 under Windows). This is the same id that would be used if "run_as_current_user" was set to `true`. Example usage:: something: '{{ os_group() }}' Example result:: something: '100' """ return str(getgid())
def exec_fg(client, project: Project, service_name: str, cmd: str, cols=None, lines=None, root=False, environment_variables=None) -> int: """Open an interactive shell to one running service container""" if service_name not in project["app"]["services"]: raise ExecError("Service not found.") if environment_variables is None: environment_variables = {} container_name = get_service_container_name(project["name"], service_name) service_obj = project["app"]["services"][service_name] user = getuid() user_group = getgid() try: container = client.containers.get(container_name) if container.status == "exited": container.remove() raise ExecError('The service is not running. Try starting it first.') # TODO: The Docker Python API doesn't seem to support interactive exec - use pty.spawn for now shell = ["docker", "exec", "-it"] if not root: shell += ["-u", str(user) + ":" + str(user_group)] if cols and lines: # Add COLUMNS and LINES env variables shell += ['-e', 'COLUMNS=' + str(cols), '-e', 'LINES=' + str(lines)] for key, value in environment_variables.items(): shell += ['-e', key + '=' + value] if "src" in service_obj["roles"]: # Service has source code, set workdir in container to current workdir shell += ["-w", CONTAINER_SRC_PATH + "/" + get_current_relative_src_path(project)] shell += [container_name, "sh", "-c", cmd] return _spawn(shell) except NotFound: raise ExecError('The service is not running. Try starting it first.') except APIError as err: raise ExecError('Error communicating with the Docker Engine.') from err
def cmd_detached(client: DockerClient, project: 'Project', command: 'Command', run_as_root=False) -> (int, str): """See AbstractEngine.cmd_detached.""" name = get_container_name(project["name"]) # Pulling image # Check if image exists try: client.images.get(command["image"]) except NotFound: image_name_full = command['image'] if ":" in command[ 'image'] else command['image'] + ":latest" client.api.pull(image_name_full) image = client.images.get(command["image"]) image_config = client.api.inspect_image(command["image"])["Config"] builder = ContainerBuilder( command["image"], command["command"] if "command" in command else image_config["Cmd"]) builder.set_name(get_container_name(project["name"])) builder.set_network(get_network_name(project["name"])) builder.set_env(EENV_NO_STDOUT_REDIRECT, "yes") builder.init_from_command(command, image_config) if not run_as_root: builder.set_env(EENV_RUN_MAIN_CMD_AS_USER, "yes") builder.set_env(EENV_USER, str(getuid())) builder.set_env(EENV_GROUP, str(getgid())) try: container = client.containers.create(**builder.build_docker_api()) add_network_links(client, container, None, project["links"]) container.start() exit_code = container.wait() output = container.logs() return exit_code['StatusCode'], output except ContainerError as err: return err.exit_status, err.stderr
def test_additional_volumes(self): for project_ctx in load(self, ['integration_all.yml'], ['.', 'src']): with project_ctx as loaded: project = loaded.config["project"] service_name = "additional_volumes" service = project["app"]["services"][service_name] # host paths host_in_volume_path_rw = os.path.join(loaded.temp_dir, '_riptide', 'data', service_name, 'in_volume_path_rw') host_in_volume_path_rw_explicit = os.path.join( loaded.temp_dir, '_riptide', 'data', service_name, 'in_volume_path_rw_explicit') host_in_volume_path_ro = os.path.join(loaded.temp_dir, '_riptide', 'data', service_name, 'in_volume_path_ro') host_in_volume_path_named = os.path.join( loaded.temp_dir, '_riptide', 'data', service_name, 'named') host_relative_to_project = os.path.join( loaded.temp_dir, 'relative_to_project') host_test_auto_create = os.path.join(loaded.temp_dir, '_riptide', 'data', service_name, 'test_auto_create') host_type_file = os.path.join(loaded.temp_dir, '_riptide', 'data', service_name, 'type_file') # container paths cnt_in_volume_path_rw = '/in_volume_path_rw' cnt_in_volume_path_rw_explicit = '/in_volume_path_rw_explicit' cnt_in_volume_path_ro = '/in_volume_path_ro' cnt_in_volume_path_named = '/in_volume_path_named' cnt_relative_to_project = str( PurePosixPath(CONTAINER_SRC_PATH).joinpath( 'relative_to_src')) cnt_test_auto_create = '/test_auto_create' cnt_type_file = '/test_auto_create' # Create most volume mounts os.makedirs(host_in_volume_path_rw) os.makedirs(host_in_volume_path_rw_explicit) os.makedirs(host_in_volume_path_ro) os.makedirs(host_in_volume_path_named) os.makedirs(host_relative_to_project) # create 'src' os.makedirs(os.path.join(loaded.temp_dir, loaded.src), exist_ok=True) ### # Create some files open(os.path.join(host_in_volume_path_rw, 'rw1'), 'a').close() open(os.path.join(host_in_volume_path_rw, 'rw2'), 'a').close() # (no in rw_explicit) open(os.path.join(host_in_volume_path_ro, 'ro1'), 'a').close() open(os.path.join(host_relative_to_project, 'rtp1'), 'a').close() open(os.path.join(host_relative_to_project, 'rtp2'), 'a').close() open(os.path.join(host_relative_to_project, 'rtp3'), 'a').close() open(os.path.join(host_in_volume_path_named, 'named'), 'a').close() ### # START self.run_start_test(loaded.engine, project, [service_name], loaded.engine_tester) # Assert volume mounts on host there self.assertTrue(os.path.isdir(host_in_volume_path_rw)) self.assertTrue(os.path.isdir(host_in_volume_path_rw_explicit)) self.assertTrue(os.path.isdir(host_in_volume_path_ro)) self.assertTrue(os.path.isdir(host_relative_to_project)) self.assertTrue(os.path.isdir(host_test_auto_create)) self.assertTrue(os.path.isfile(host_type_file)) self.assertTrue(os.path.isdir(host_in_volume_path_named)) # Assert volume mounts in container there loaded.engine_tester.assert_file_exists( cnt_in_volume_path_rw, loaded.engine, project, service) loaded.engine_tester.assert_file_exists( cnt_in_volume_path_rw_explicit, loaded.engine, project, service) loaded.engine_tester.assert_file_exists( cnt_in_volume_path_ro, loaded.engine, project, service) loaded.engine_tester.assert_file_exists( cnt_relative_to_project, loaded.engine, project, service) loaded.engine_tester.assert_file_exists( cnt_test_auto_create, loaded.engine, project, service) loaded.engine_tester.assert_file_exists( cnt_type_file, loaded.engine, project, service) loaded.engine_tester.assert_file_exists( cnt_in_volume_path_named, loaded.engine, project, service) # Assert files there in container loaded.engine_tester.assert_file_exists( PurePosixPath(cnt_in_volume_path_rw).joinpath('rw1'), loaded.engine, project, service) loaded.engine_tester.assert_file_exists( PurePosixPath(cnt_in_volume_path_rw).joinpath('rw2'), loaded.engine, project, service) loaded.engine_tester.assert_file_exists( PurePosixPath(cnt_in_volume_path_ro).joinpath('ro1'), loaded.engine, project, service) loaded.engine_tester.assert_file_exists( PurePosixPath(cnt_relative_to_project).joinpath('rtp1'), loaded.engine, project, service) loaded.engine_tester.assert_file_exists( PurePosixPath(cnt_relative_to_project).joinpath('rtp2'), loaded.engine, project, service) loaded.engine_tester.assert_file_exists( PurePosixPath(cnt_relative_to_project).joinpath('rtp3'), loaded.engine, project, service) loaded.engine_tester.assert_file_exists( PurePosixPath(cnt_in_volume_path_named).joinpath('named'), loaded.engine, project, service) # Assert relative_to_src diectory there on host # (from mounting relative_to_project it inside /src on container ) host_relative_to_src = os.path.join(loaded.temp_dir, loaded.src, 'relative_to_src') self.assertTrue(os.path.isdir(host_relative_to_src)) # Even though the directory must exist, the files must NOT due to the way mounting works on linux. self.assertFalse( os.path.isfile(os.path.join(host_relative_to_src, 'rtp1'))) self.assertFalse( os.path.isfile(os.path.join(host_relative_to_src, 'rtp2'))) self.assertFalse( os.path.isfile(os.path.join(host_relative_to_src, 'rtp3'))) # Add files on host open(os.path.join(host_in_volume_path_rw, 'rw_added'), 'a').close() open( os.path.join(host_in_volume_path_rw_explicit, 'rw_explicit_added'), 'a').close() # Assert added files there in container loaded.engine_tester.assert_file_exists( PurePosixPath(cnt_in_volume_path_rw).joinpath('rw_added'), loaded.engine, project, service) loaded.engine_tester.assert_file_exists( PurePosixPath(cnt_in_volume_path_rw_explicit).joinpath( 'rw_explicit_added'), loaded.engine, project, service) # Add files in container loaded.engine_tester.create_file(PurePosixPath( cnt_in_volume_path_rw).joinpath('rw_added_in_container'), loaded.engine, project, service, as_user=cpuser.getuid()) # Assert added files there on host self.assertTrue( os.path.isfile( os.path.join(host_in_volume_path_rw, 'rw_added_in_container'))) # Assert permissions rw user1, group1, mode1, write_check = loaded.engine_tester.get_permissions_at( '/in_volume_path_rw', loaded.engine, project, service, as_user=cpuser.getuid()) self.assertEqual(cpuser.getuid(), user1, 'The current user needs to own the volume') self.assertEqual( cpuser.getgid(), group1, 'The current group needs to be the group of the volume') self.assertTrue(bool(mode1 & stat.S_IRUSR), 'The volume must be readable by user') self.assertTrue(bool(mode1 & stat.S_IWUSR), 'The volume must be writable by group') self.assertTrue( write_check, 'The volume has to be ACTUALLY writable by user; files must be creatable.' ) # Assert permissions ro user, group, mode, write_check = loaded.engine_tester.get_permissions_at( '/in_volume_path_ro', loaded.engine, project, service, as_user=cpuser.getuid()) self.assertEqual(cpuser.getuid(), user1, 'The current user needs to own the volume') self.assertEqual( cpuser.getgid(), group1, 'The current group needs to be the group of the volume') self.assertTrue(bool(mode1 & stat.S_IRUSR), 'The volume must be readable by user') self.assertTrue(bool(mode1 & stat.S_IWUSR), 'The volume must be writable by group') self.assertFalse( write_check, 'The volume has to be NOT ACTUALLY writable by user; ' 'files must NOT be creatable.') # STOP self.run_stop_test(loaded.engine, project, [service_name], loaded.engine_tester)
def test_with_src(self): for project_ctx in load(self, ['integration_all.yml'], ['.', 'src']): with project_ctx as loaded: project = loaded.config["project"] service_name = "simple_with_src" # Put a index.html file into the root of the project folder and one in the src folder, depending on # what src we are testing right now, we will expect a different file to be served. index_file_in_dot = b'hello dot\n' index_file_in_src = b'hello src\n' with open(os.path.join(loaded.temp_dir, 'index.html'), 'wb') as f: f.write(index_file_in_dot) os.makedirs(os.path.join(loaded.temp_dir, 'src')) with open(os.path.join(loaded.temp_dir, 'src', 'index.html'), 'wb') as f: f.write(index_file_in_src) # START self.run_start_test(loaded.engine, project, [service_name], loaded.engine_tester) # Check response if loaded.src == '.': self.assert_response(index_file_in_dot, loaded.engine, project, service_name) elif loaded.src == 'src': self.assert_response(index_file_in_src, loaded.engine, project, service_name) else: AssertionError('Error in test: Unexpected src') # Check permissions user, group, mode, write_check = loaded.engine_tester.get_permissions_at( '.', loaded.engine, project, project["app"]["services"][service_name], as_user=cpuser.getuid()) # we use the cpuser module so this technically also works on windows because the cpuser module returns 0 # and Docker mounts for root. self.assertEqual( cpuser.getuid(), user, 'The current user needs to own the src volumes') self.assertEqual( cpuser.getgid(), group, 'The current group needs to be the group of the src volumes' ) self.assertTrue(bool(mode & stat.S_IRUSR), 'The src volume must be readable by owner') self.assertTrue(bool(mode & stat.S_IWUSR), 'The src volume must be writable by owner') self.assertTrue( write_check, 'The src volume must be ACTUALLY writable by owner') # STOP self.run_stop_test(loaded.engine, project, [service_name], loaded.engine_tester)
def test_configs(self): for project_ctx in load(self, ['integration_all.yml'], ['.']): with project_ctx as loaded: engine = loaded.engine project = loaded.config["project"] service_name = "configs" service = project["app"]["services"][service_name] # START self.run_start_test(loaded.engine, project, [service_name], loaded.engine_tester) # Assert existing and content self.assertEqual( 'no_variable_just_text', loaded.engine_tester.get_file('/config1', loaded.engine, project, service)) self.assertEqual( project["app"]["name"], loaded.engine_tester.get_file('/config2', loaded.engine, project, service)) # Assert permissions user1, group1, mode1, write_check1 = loaded.engine_tester.get_permissions_at( '/config1', loaded.engine, project, service, is_directory=False, as_user=cpuser.getuid()) self.assertEqual( cpuser.getuid(), user1, 'The current user needs to own the config file') self.assertEqual( cpuser.getgid(), group1, 'The current group needs to be the group of the config file' ) self.assertTrue(bool(mode1 & stat.S_IRUSR), 'The config file must be readable by owner') self.assertTrue(bool(mode1 & stat.S_IWUSR), 'The config file must be writable by owner') self.assertTrue( write_check1, 'The config file must be ACTUALLY writable by owner') user2, group2, mode2, write_check2 = loaded.engine_tester.get_permissions_at( '/config2', loaded.engine, project, service, is_directory=False, as_user=cpuser.getuid()) self.assertEqual( cpuser.getuid(), user2, 'The current user needs to own the config file') self.assertEqual( cpuser.getgid(), group2, 'The current group needs to be the group of the config file' ) self.assertTrue(bool(mode2 & stat.S_IRUSR), 'The config file must be readable by owner') self.assertTrue(bool(mode2 & stat.S_IWUSR), 'The config file must be writable by owner') self.assertTrue( write_check2, 'The config file must be ACTUALLY writable by owner') # Assert that files _riptide/config/NAME_WITH_DASHES have been created self.assertTrue( os.path.isfile( os.path.join(loaded.temp_dir, '_riptide', 'processed_config', service_name, 'one'))) self.assertTrue( os.path.isfile( os.path.join(loaded.temp_dir, '_riptide', 'processed_config', service_name, 'two'))) # STOP self.run_stop_test(loaded.engine, project, [service_name], loaded.engine_tester)