示例#1
0
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())
示例#3
0
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)