-
Notifications
You must be signed in to change notification settings - Fork 0
gh17 docker mount coverity #22
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
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Updates sc docker run to support Coverity by mounting the host’s Coverity install into containers (replacing prior CodeSonar-specific mounting) and adjusting the container command invocation accordingly.
Changes:
- Add automatic detection of
/opt/coverity(symlink) and mount its resolved target into the container. - Modify the generated container command to export Coverity’s
bindirectory ontoPATH. - Remove the previous CodeSonar mount / environment injection.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/sc/docker/docker.py
Outdated
| coverity_dir = Path('/opt/coverity').resolve() | ||
| if Path('/opt/coverity').is_symlink() and Path(coverity_dir).exists(): | ||
| docker_args += ['-v', f"{coverity_dir}:{coverity_dir}"] |
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Coverity mount currently mounts the resolved target path back onto the same resolved path inside the container, but _generate_bash_command later hard-codes /opt/coverity/bin into PATH. If /opt/coverity is a symlink (per the issue), the container may not have /opt/coverity pointing at the mounted directory, so /opt/coverity/bin won’t exist. Consider mounting the resolved host directory to /opt/coverity in-container (and optionally mark it read-only), or otherwise ensure /opt/coverity exists in the container and points at the mounted path.
| coverity_dir = Path('/opt/coverity').resolve() | |
| if Path('/opt/coverity').is_symlink() and Path(coverity_dir).exists(): | |
| docker_args += ['-v', f"{coverity_dir}:{coverity_dir}"] | |
| coverity_path = Path('/opt/coverity') | |
| coverity_dir = coverity_path.resolve() | |
| if coverity_path.exists(): | |
| docker_args += ['-v', f"{coverity_dir}:/opt/coverity:ro"] |
src/sc/docker/docker.py
Outdated
| coverity_dir = Path('/opt/coverity').resolve() | ||
| if Path('/opt/coverity').is_symlink() and Path(coverity_dir).exists(): | ||
| docker_args += ['-v', f"{coverity_dir}:{coverity_dir}"] |
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR/issue description says Coverity should be mounted “if arg is supplied”, but this code mounts Coverity automatically whenever /opt/coverity is a symlink on the host. There’s no corresponding CLI option in sc/docker_cli.py to enable/disable this behavior. Either add an explicit flag/option and thread it through, or update the PR description/issue expectation to match the implemented behavior.
src/sc/docker/docker.py
Outdated
| bash_command = f"bash -c 'source /usr/local/bin/bashext.sh && export PATH=/opt/coverity/bin:$PATH && cd {Path.cwd()}; {' '.join(command)}'" | ||
| if x11 and os.getenv("DISPLAY"): | ||
| bash_command += self._setup_xauth(container_name) | ||
| return bash_command + " ".join(command) | ||
| return bash_command |
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bash_command opens a single-quoted script (bash -c '...'), but when X11 is enabled you append the xauth setup string after that closing quote, which produces an invalid command string and likely breaks --x11 runs. Build the full script (including xauth setup) inside the bash -c payload, or pass bash, -c, and the script as separate docker args so quoting is handled correctly.
src/sc/docker/docker.py
Outdated
| bash_command = f"bash -c 'source /usr/local/bin/bashext.sh && export PATH=/opt/coverity/bin:$PATH && cd {Path.cwd()}; {' '.join(command)}'" | ||
| if x11 and os.getenv("DISPLAY"): |
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using single quotes around the entire bash -c payload prevents shell expansion inside the user-provided command (e.g., echo $HOME won’t expand) and will break commands containing single quotes. Prefer passing the command as separate argv items (exec form) or construct the bash -c payload with proper escaping (or use double quotes where appropriate) to preserve expected shell behavior.
db28c1d to
65d5abb
Compare
|
b'## WARNING: A Blackduck scan failure has been waived A prior failure has been upvoted
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/sc/docker/docker.py
Outdated
|
|
||
| def _execute_docker_run(self, docker_command: list, image: str): | ||
| """Prints and executes the docker run command.""" | ||
| print(docker_command) |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
print(docker_command) looks like a debug leftover and will emit an unformatted Python list in addition to the existing click.secho output. Consider removing it or using the existing logging/output mechanism so CLI output stays stable.
| print(docker_command) |
src/sc/docker/docker.py
Outdated
| if cmd: | ||
| parts.append(cmd) | ||
|
|
||
| bash = "bash -c '" + " && ".join(parts) + "'" |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_generate_bash_command wraps the full command string in single quotes (bash -c '...') but does not escape embedded single quotes coming from setup_cmds, Path.cwd(), or command. This will break valid inputs (and can change semantics) when any part contains a '. Build the shell command using proper escaping (e.g., quoting each segment/argument) or avoid the nested single-quote construction.
| bash = "bash -c '" + " && ".join(parts) + "'" | |
| command_str = " && ".join(parts) | |
| # Escape single quotes so the command is safe inside a single-quoted bash -c argument. | |
| escaped_command_str = command_str.replace("'", "'\"'\"'") | |
| bash = "bash -c '" + escaped_command_str + "'" |
src/sc/docker/docker.py
Outdated
| if xauth_line: | ||
| hostname = os.getenv("HOSTNAME") | ||
| xauth_line = f"{xauth_line}/{hostname}/{container_name}" | ||
| return f"touch {Path.home()}/.Xauthority; xauth add {xauth_line}; " | ||
|
|
||
| prelude_cmd = [f"touch {Path.home()}/.Xauthority", f"xauth add {xauth_line}"] | ||
|
|
||
| return docker_args, prelude_cmd |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_add_x11_support can raise an UnboundLocalError: prelude_cmd is only assigned inside if xauth_line: but is returned unconditionally. Initialize prelude_cmd to an empty list before the try/except and return docker_args, [] when no xauth entry is found.
src/sc/docker/docker.py
Outdated
| def _add_coverity_mount(self) -> tuple[list[str], list[str]]: | ||
| coverity_dir = Path('/opt/coverity').resolve() | ||
| if Path('/opt/coverity').is_symlink() and Path(coverity_dir).exists(): | ||
| coverity_args = ['-v', f"{coverity_dir}:{coverity_dir}"] |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_add_coverity_mount exports PATH=/opt/coverity/bin:$PATH but the volume mount is created at {coverity_dir}:{coverity_dir} (the symlink target), not at /opt/coverity. If /opt/coverity doesn’t exist inside the container, the PATH update points to a non-mounted directory and Coverity won’t be usable. Mount the host Coverity install to /opt/coverity in the container (or update PATH to use the mounted destination path consistently).
| coverity_args = ['-v', f"{coverity_dir}:{coverity_dir}"] | |
| coverity_args = ['-v', f"{coverity_dir}:/opt/coverity"] |
ff3a39f to
387a564
Compare
387a564 to
f8939cc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/sc/docker/docker.py
Outdated
| coverity_args = ['-v', f"{coverity_dir}:{coverity_dir}"] | ||
| coverity_prelude = [f"export PATH={coverity_dir}:$PATH"] |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_add_coverity_mount() resolves /opt/coverity and then bind-mounts the resolved path into the container at the same resolved path. This does not satisfy the requirement to mount /opt/coverity inside the container (tools inside the container will look for /opt/coverity, not the host’s resolved target path). Consider mounting the resolved target to /opt/coverity in the container (or simply mounting /opt/coverity:/opt/coverity if Docker resolves the symlink correctly). Also, the PATH prelude currently adds the install root to PATH; typically the executables are under a bin/ directory, so PATH should likely include the correct bin location under the mounted /opt/coverity.
| coverity_args = ['-v', f"{coverity_dir}:{coverity_dir}"] | |
| coverity_prelude = [f"export PATH={coverity_dir}:$PATH"] | |
| # Bind-mount the resolved host directory into the container at /opt/coverity | |
| coverity_args = ['-v', f"{coverity_dir}:/opt/coverity"] | |
| # Add the Coverity bin directory under the mounted /opt/coverity to PATH | |
| coverity_prelude = ["export PATH=/opt/coverity/bin:$PATH"] |
src/sc/docker/docker.py
Outdated
| coverity_args, coverity_setup_cmds = self._add_coverity_mount() | ||
| docker_args += coverity_args | ||
| setup_cmds += coverity_setup_cmds |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Coverity is mounted unconditionally whenever /opt/coverity exists as a symlink on the host. Issue #17/PR description indicate the mount should happen only when an argument/flag is supplied. If the CLI is intended to control this behavior, thread a boolean/option down into _generate_docker_run_command() and guard _add_coverity_mount() accordingly.
src/sc/docker/docker.py
Outdated
| cmd = " ".join(command) | ||
| parts = [ | ||
| "source /usr/local/bin/bashext.sh", | ||
| *setup_cmds, | ||
| f"cd {Path.cwd()}" |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_generate_bash_command() builds cmd via " ".join(command), which loses correct argument quoting/escaping when any argument contains spaces or shell metacharacters. Since this function is explicitly shell-escaping, use a join that preserves argument boundaries (e.g., shlex.join(command)), and also ensure the cd target is shell-quoted so paths with spaces don’t break.
| cmd = " ".join(command) | |
| parts = [ | |
| "source /usr/local/bin/bashext.sh", | |
| *setup_cmds, | |
| f"cd {Path.cwd()}" | |
| cmd = shlex.join(command) | |
| parts = [ | |
| "source /usr/local/bin/bashext.sh", | |
| *setup_cmds, | |
| f"cd {shlex.quote(str(Path.cwd()))}" |
src/sc/docker/docker.py
Outdated
|
|
||
| if xauth_line: | ||
| hostname = os.getenv("HOSTNAME") | ||
| xauth_line = f"{xauth_line}/{hostname}/{container_name}" |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In _add_x11_support(), xauth_line is taken from xauth list (which already returns a complete xauth add-compatible record) and then has /{hostname}/{container_name} appended. This appears to corrupt the record by modifying the cookie/token portion rather than adjusting only the display name. If the goal is to rewrite the first field (display) for the container, parse the output and replace just that field; otherwise, pass the xauth list output through unchanged.
| xauth_line = f"{xauth_line}/{hostname}/{container_name}" | |
| if hostname: | |
| parts = xauth_line.split() | |
| if len(parts) >= 3: | |
| parts[0] = f"{parts[0]}/{hostname}/{container_name}" | |
| xauth_line = " ".join(parts) |
f8939cc to
5c14a25
Compare
TB-1993
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return this as tuples only to reconnect them after is pretty horrible.
I'd suggest either:
- Make use of pythons pass by object reference approach and pass the docker_arg list into the functions and make the necessary modifications to it.
- Keep the args for
docker runand the args to the commands to be run in the container in two separate lists.
5c14a25 to
85797d3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/sc/docker/docker.py
Outdated
| def _add_coverity_mount(self, docker_run: DockerRunBuilder): | ||
| coverity_dir = Path('/opt/coverity').resolve() | ||
| if Path('/opt/coverity').is_symlink() and Path(coverity_dir).exists(): | ||
| docker_run.add_volume_mount(f"{coverity_dir}:{coverity_dir}") | ||
| docker_run.add_bash_command(f"export PATH={coverity_dir}:$PATH") |
Copilot
AI
Feb 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Coverity mount uses the resolved symlink target as both source and destination ({coverity_dir}:{coverity_dir}). The linked issue describes Coverity being available via /opt/coverity; mounting to the resolved path means the container may not have /opt/coverity at all. Consider mounting to /opt/coverity:/opt/coverity (or ensuring the container path /opt/coverity exists) so callers can rely on the documented location.
src/sc/docker/docker_run_builder.py
Outdated
| if value: | ||
| self.docker_args += ['-e', f"{var_name}={value}"] | ||
| else: |
Copilot
AI
Feb 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add_env_var() checks if value: which treats valid values like 0 (e.g., uid/gid when running as root) as falsy and will emit -e VAR instead of -e VAR=0. Use an explicit value is not None check (and stringify non-str values) so zero/empty-string values are handled correctly.
| if value: | |
| self.docker_args += ['-e', f"{var_name}={value}"] | |
| else: | |
| if value is not None: | |
| # Ensure all non-None values (including falsy ones like 0 or "") are passed as VAR=<value> | |
| self.docker_args += ['-e', f"{var_name}={value!s}"] | |
| else: | |
| # Preserve behavior for flag-style environment variables with no explicit value |
src/sc/docker/docker_run_builder.py
Outdated
| @@ -0,0 +1,48 @@ | |||
| import shlex | |||
| from pathlib import Path | |||
Copilot
AI
Feb 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Path is imported but not used in this module. Removing it avoids dead imports and keeps the file lint-clean.
| from pathlib import Path |
| from netrc import netrc, NetrcParseError | ||
| import os | ||
| from pathlib import Path | ||
| import shlex |
Copilot
AI
Feb 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shlex is imported but never used in this file. Please remove the unused import to avoid dead code / lint failures.
| import shlex |
src/sc/docker/docker.py
Outdated
| sys.exit(f"You cannot mount in any of the following directories: {', '.join(BANNED_MOUNT_DIRS)}") | ||
| raise ScDockerException( | ||
| f"You cannot mount to {dest_dir}! You cannot mount " | ||
| f"in any of the followind directories {', '.join(BANNED_MOUNT_DIRS)}" |
Copilot
AI
Feb 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in error message: "followind" → "following" (and consider adding a colon before the directory list for readability).
| f"in any of the followind directories {', '.join(BANNED_MOUNT_DIRS)}" | |
| f"in any of the following directories: {', '.join(BANNED_MOUNT_DIRS)}" |
src/sc/docker/docker.py
Outdated
|
|
||
| def _validate_docker_mount_source(self, server_dir: str): | ||
| if not os.path.isdir(server_dir): | ||
| sys.exit(f"{server_dir} does not exist") |
Copilot
AI
Feb 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_validate_docker_mount_source() still calls sys.exit() when the source directory doesn't exist, while other mount validation errors now raise ScDockerException and are handled by the caller. For consistent error handling (and to avoid exiting from deep helpers), raise ScDockerException here as well and let run() decide how to exit.
| sys.exit(f"{server_dir} does not exist") | |
| raise ScDockerException(f"{server_dir} does not exist") |
src/sc/docker/docker.py
Outdated
| if xauth_line: | ||
| hostname = os.getenv("HOSTNAME") | ||
| xauth_line = f"{xauth_line}/{hostname}/{container_name}" | ||
| return f"touch {Path.home()}/.Xauthority; xauth add {xauth_line}; " | ||
|
|
||
| def _add_user_env_vars(self) -> list: | ||
| docker_args = [ | ||
| '-e', f"LOCAL_USER_NAME={getpass.getuser()}", | ||
| '-e', f"LOCAL_USER_ID={os.getuid()}", | ||
| '-e', f"LOCAL_GROUP_ID={os.getgid()}", | ||
| '-e', f"LOCAL_START_DIR={Path.home()}", | ||
| ] | ||
| try: | ||
| docker_group_id = grp.getgrnam('docker').gr_gid | ||
| docker_args += ['-e', f"LOCAL_DOCKER_GROUP={docker_group_id}"] | ||
| except: | ||
| pass | ||
| return docker_args | ||
|
|
||
| def _add_interactive_flag(self) -> list: | ||
| # If stdout is connected to a terminal | ||
| return ['-it'] if os.isatty(sys.stdout.fileno()) else [] | ||
| xauth_line = xauth_line.replace(hostname, container_name, 1) | ||
| docker_run.add_bash_command(f"touch $HOME/.Xauthority") |
Copilot
AI
Feb 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In _add_x11_support, hostname = os.getenv("HOSTNAME") can be None. Passing None into xauth_line.replace(hostname, ...) will raise a TypeError and break --x11 runs. Guard for a missing HOSTNAME (e.g., fallback to socket.gethostname() or skip the replacement when unset).
src/sc/docker/docker.py
Outdated
| try: | ||
| docker_group_id = grp.getgrnam('docker').gr_gid | ||
| docker_run.add_env_var("LOCAL_DOCKER_GROUP", docker_group_id) | ||
| except: |
Copilot
AI
Feb 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except block directly handles BaseException.
| except: | |
| except KeyError: |
src/sc/docker/docker.py
Outdated
| try: | ||
| docker_group_id = grp.getgrnam('docker').gr_gid | ||
| docker_run.add_env_var("LOCAL_DOCKER_GROUP", docker_group_id) | ||
| except: |
Copilot
AI
Feb 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'except' clause does nothing but pass and there is no explanatory comment.
| except: | |
| except KeyError: | |
| # If the 'docker' group does not exist on this system, skip setting LOCAL_DOCKER_GROUP. |
85797d3 to
7371a6e
Compare
7371a6e to
88e818e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 12 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/sc/docker/docker.py
Outdated
| return ['-it'] if os.isatty(sys.stdout.fileno()) else [] | ||
| xauth_line = xauth_line.replace(hostname, container_name, 1) | ||
| docker_run.add_bash_command(f"touch $HOME/.Xauthority") | ||
| docker_run.add_bash_command(f"xauth add {xauth_line}") |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential command injection vulnerability. The xauth_line from subprocess.check_output is used directly in a bash command without proper escaping. If the xauth output contains shell metacharacters, they could be executed. Use shlex.quote() to safely escape the xauth_line before using it in the bash command: docker_run.add_bash_command(f"xauth add {shlex.quote(xauth_line)}")
src/sc/docker/docker.py
Outdated
| return docker_args | ||
| self._add_coverity_mount(docker_run) | ||
|
|
||
| docker_run.add_bash_command(f"cd {Path.cwd()}") |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Path.cwd() returns a Path object but should be converted to a string. While this will work due to implicit string conversion in the f-string, it's better to be explicit: f"cd {str(Path.cwd())}" or use Path.cwd().as_posix() for a portable path string.
src/sc/docker/docker.py
Outdated
| docker_run.add_env_var("LOCAL_USER_ID", os.getuid()) | ||
| docker_run.add_env_var("LOCAL_GROUP_ID", os.getgid()) | ||
| docker_run.add_env_var("LOCAL_START_DIR", Path.home()) | ||
| try: | ||
| docker_group_id = grp.getgrnam('docker').gr_gid | ||
| docker_run.add_env_var("LOCAL_DOCKER_GROUP", docker_group_id) |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type annotations don't match actual parameter types being passed. The add_env_var method signature expects value: str | None, but non-string values are being passed:
- Line 527: os.getuid() returns int
- Line 528: os.getgid() returns int
- Line 529: Path.home() returns Path
- Line 532: docker_group_id is int
While Python's f-string will convert these to strings at runtime, this violates the type contract and will cause type checker errors. Either update the method signature to accept Union[str, int, Path, None] or explicitly convert values to str before passing them.
| docker_run.add_env_var("LOCAL_USER_ID", os.getuid()) | |
| docker_run.add_env_var("LOCAL_GROUP_ID", os.getgid()) | |
| docker_run.add_env_var("LOCAL_START_DIR", Path.home()) | |
| try: | |
| docker_group_id = grp.getgrnam('docker').gr_gid | |
| docker_run.add_env_var("LOCAL_DOCKER_GROUP", docker_group_id) | |
| docker_run.add_env_var("LOCAL_USER_ID", str(os.getuid())) | |
| docker_run.add_env_var("LOCAL_GROUP_ID", str(os.getgid())) | |
| docker_run.add_env_var("LOCAL_START_DIR", str(Path.home())) | |
| try: | |
| docker_group_id = grp.getgrnam('docker').gr_gid | |
| docker_run.add_env_var("LOCAL_DOCKER_GROUP", str(docker_group_id)) |
src/sc/docker/docker.py
Outdated
|
|
||
| docker_run.add_bash_command(f"cd {Path.cwd()}") | ||
| if command: | ||
| docker_run.add_bash_command(" ".join(command)) |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential command injection vulnerability. The user-supplied command tuple is joined with simple spaces without proper shell escaping. If any command argument contains shell metacharacters (spaces, quotes, semicolons, etc.), they could cause unexpected behavior or command injection. Use shlex.join(command) instead of " ".join(command) to properly escape shell arguments. Note that shlex is already imported in docker_run_builder.py and would need to be imported in docker.py as well.
src/sc/docker/docker.py
Outdated
| xauth_line = xauth_line.replace(hostname, container_name, 1) | ||
| docker_run.add_bash_command(f"touch $HOME/.Xauthority") | ||
| docker_run.add_bash_command(f"xauth add {xauth_line}") |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The xauth line manipulation is incorrect and breaks X11 authentication. The old implementation formatted the xauth line as "{xauth_line}/{hostname}/{container_name}" (visible in removed code context), but this new code uses replace(hostname, container_name, 1) which replaces the first occurrence of hostname within the xauth entry itself. This fundamentally changes the xauth entry format instead of appending the hostname/container information as needed. The original xauth entry format should be preserved and the hostname/container name should be appended or handled differently according to xauth requirements.
| xauth_line = xauth_line.replace(hostname, container_name, 1) | |
| docker_run.add_bash_command(f"touch $HOME/.Xauthority") | |
| docker_run.add_bash_command(f"xauth add {xauth_line}") | |
| # Preserve the original xauth entry and append hostname/container info | |
| suffix_parts = [] | |
| if hostname: | |
| suffix_parts.append(hostname) | |
| if container_name: | |
| suffix_parts.append(container_name) | |
| if suffix_parts: | |
| xauth_entry = f"{xauth_line}/" + "/".join(suffix_parts) | |
| else: | |
| xauth_entry = xauth_line | |
| docker_run.add_bash_command(f"touch $HOME/.Xauthority") | |
| docker_run.add_bash_command(f"xauth add {xauth_entry}") |
src/sc/docker/docker_run_builder.py
Outdated
| @@ -0,0 +1,47 @@ | |||
| import shlex | |||
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing copyright header. All other Python files in the codebase (e.g., docker.py, config_manager.py, docker_cli.py) include the Apache 2.0 copyright header at the top of the file. This file should include the same header for consistency and legal compliance.
src/sc/docker/docker.py
Outdated
| def _add_interactive_flag(self) -> list: | ||
| # If stdout is connected to a terminal | ||
| return ['-it'] if os.isatty(sys.stdout.fileno()) else [] | ||
| xauth_line = xauth_line.replace(hostname, container_name, 1) |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential TypeError when hostname is None. If the HOSTNAME environment variable is not set, os.getenv("HOSTNAME") returns None, and calling str.replace(None, ...) will raise a TypeError. Add a check to handle the case when hostname is None, or provide a default value: hostname = os.getenv("HOSTNAME") or "localhost"
| xauth_line = xauth_line.replace(hostname, container_name, 1) | |
| if hostname: | |
| xauth_line = xauth_line.replace(hostname, container_name, 1) |
src/sc/docker/docker_run_builder.py
Outdated
| @@ -0,0 +1,47 @@ | |||
| import shlex | |||
|
|
|||
| class DockerRunBuilder: | |||
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing documentation for the DockerRunBuilder class. The class lacks a docstring explaining its purpose, which would help future maintainers understand its role in building Docker run commands. Add a class-level docstring following the pattern used in the SCDocker class.
| class DockerRunBuilder: | |
| class DockerRunBuilder: | |
| """ | |
| Helper for constructing `docker run` command invocations. | |
| This builder assembles the arguments for a `docker run` command, | |
| including container name, hostname, volume mounts, environment | |
| variables, and interactive flags, and composes a bash command | |
| sequence to be executed inside the container. | |
| """ |
src/sc/docker/docker_run_builder.py
Outdated
| def add_volume_mount(self, volume: str): | ||
| self.docker_args += ['-v', volume] | ||
|
|
||
| def add_env_var(self, var_name: str, value: str = None): |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type hint for value parameter is incorrect. The parameter is typed as str = None, but None is not a string. This should be value: str | None = None (Python 3.10+) or value: Optional[str] = None (with from typing import Optional). The current annotation will cause type checking tools to flag this as an error.
| @@ -0,0 +1,4 @@ | |||
|
|
|||
|
|
|||
| class ScDockerException(Exception): | |||
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing documentation for the ScDockerException class. The custom exception class lacks a docstring explaining when it should be raised and what it represents. Add a class-level docstring to clarify its purpose, e.g., "Raised when sc-docker operations encounter errors related to Docker configuration or execution."
| class ScDockerException(Exception): | |
| class ScDockerException(Exception): | |
| """Raised when sc-docker operations encounter errors related to Docker configuration or execution.""" |
88e818e to
1be4ade
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/sc/docker/docker.py
Outdated
| self._handle_invalid_tag(image, tag, tags) | ||
| else: | ||
| self._check_no_registries() | ||
| images = self._get_remote_images() |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_get_remote_images() is called without the required image_ref argument. As written this will raise a TypeError and prevent remote images from running. Pass image_ref through (or change _get_remote_images to take no args).
| images = self._get_remote_images() | |
| images = self._get_remote_images(image_ref) |
src/sc/docker/docker.py
Outdated
| tags = self._fetch_remote_tags(image, registry_url) | ||
| if tag not in tags: | ||
| self._handle_invalid_tag(image, tag, tags) |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_fetch_remote_tags expects (image, username, api_token), but here it’s called with (image, registry_url). This will fail at runtime and also won’t authenticate correctly. Use _get_registry_creds_by_url(registry_url) (or the existing _fetch_tags(...) helper) to supply the proper credentials.
src/sc/docker/docker.py
Outdated
| def _get_remote_images(self, image_ref: str) -> str: | ||
| images = [] | ||
|
|
||
| if local: | ||
| images = list({tag.split(":")[0] for image in self.docker_client.images.list() for tag in image.tags}) | ||
|
|
||
| # If image ref matches a host just get that hosts images. Otherwise get all hosts images. | ||
| host = False | ||
| for registry_url in self.docker_config: | ||
| if image_ref.startswith(registry_url): | ||
| host = registry_url | ||
|
|
||
| if host: | ||
| images = [f"{host}/{image}" for image in self._fetch_images_by_registry(host)] | ||
| else: | ||
| # If image ref matches a host just get that hosts images. Otherwise get all hosts images. | ||
| host = False | ||
| for registry_url in self.docker_config: | ||
| if image_ref.startswith(registry_url): | ||
| host = registry_url | ||
|
|
||
| if host: | ||
| images = [f"{host}/{image}" for image in self._fetch_images_by_registry(host)] | ||
| else: | ||
| for registry_url in self.docker_config: | ||
| registry_images = self._fetch_images_by_registry(registry_url) | ||
| images.extend([f"{registry_url}/{image}" for image in registry_images]) | ||
|
|
||
| registry_images = self._fetch_images_by_registry(registry_url) | ||
| images.extend([f"{registry_url}/{image}" for image in registry_images]) | ||
|
|
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_get_remote_images builds up an images list but never returns it, and its return type annotation is str even though it’s a list. This will currently return None and break callers. Return images and update the annotation accordingly.
src/sc/docker/docker.py
Outdated
| docker_run.add_volume_mount(f"{coverity_dir}:{coverity_dir}") | ||
| docker_run.add_bash_command(f"export PATH={coverity_dir}:$PATH") |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR/issue states Coverity is linked at /opt/coverity, but this mount uses the resolved target path for both source and destination (<resolved>:<resolved>). That means /opt/coverity inside the container may not exist/point to the mounted install, so tooling expecting /opt/coverity will fail. Mount the resolved host path into /opt/coverity in the container (and consider setting PATH relative to /opt/coverity).
| docker_run.add_volume_mount(f"{coverity_dir}:{coverity_dir}") | |
| docker_run.add_bash_command(f"export PATH={coverity_dir}:$PATH") | |
| docker_run.add_volume_mount(f"{coverity_dir}:/opt/coverity") | |
| docker_run.add_bash_command("export PATH=/opt/coverity:$PATH") |
|
|
||
| self._add_coverity_mount(docker_run) | ||
|
|
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue #17/PR description mentions mounting Coverity if an argument is supplied and that CodeSonar is optional, but _add_coverity_mount is called unconditionally and CodeSonar mounting has been removed entirely. If the intent is optional behavior, add a CLI flag and thread it through to _generate_docker_run_command so users can opt in/out (and keep CodeSonar support if still required).
src/sc/docker/docker_run_builder.py
Outdated
| def build(self) -> list[str]: | ||
| """Build the final docker run argument list.""" | ||
| return [ | ||
| *self.docker_args, | ||
| f"{self.image}:{self.tag}", | ||
| shlex.join(["bash", "-c", " && ".join(self.bash_commands)]) | ||
| ] |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
build() uses shlex.join(...) to produce a shell-escaped single string and places it as one argv element. Because the command is ultimately executed via os.execvp, those quotes/escaping become literal characters and Docker won’t split it into bash, -c, and the script. If you need exec-form args, append bash, -c, and the script as separate list elements; if you truly need a single script string, avoid shlex.join here and only use it for logging.
42a4ef3 to
3b90bdb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| coverity_dir = Path('/opt/coverity').resolve() | ||
| if Path('/opt/coverity').is_symlink() and Path(coverity_dir).exists(): | ||
| docker_args += ["-v", f"{coverity_dir}:{coverity_dir}"] | ||
| bash_commands += [f"export PATH={coverity_dir}:$PATH"] |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PATH export adds the coverity root directory to PATH, which is unusual. Most tool installations have their executables in a 'bin' subdirectory. Consider whether this should be 'export PATH={coverity_dir}/bin:$PATH' to add the bin directory instead of the root directory. This would be consistent with how CodeSonar was handled (which used CODESONAR_PATH={codesonar_dir}/codesonar/bin).
| bash_commands += [f"export PATH={coverity_dir}:$PATH"] | |
| bash_commands += [f"export PATH={coverity_dir}/bin:$PATH"] |
src/sc/docker/docker.py
Outdated
| Returns: | ||
| str: The full image name | ||
| """ | ||
| def _get_remote_images(self, image_ref: str) -> str: |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return type annotation is incorrect. The method returns a list of image strings, not a single string. Change the return type from 'str' to 'list[str]'.
| def _get_remote_images(self, image_ref: str) -> str: | |
| def _get_remote_images(self, image_ref: str) -> list[str]: |
| '-e', | ||
| f"LOCAL_START_DIR={Path.home()}" | ||
| ] | ||
|
|
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The architecture detection logic was removed in this refactoring. Previously, images with '_i386' or '_amd64' suffixes would automatically get the appropriate '--platform' flag. Without this, containers may fail to run on systems where the image architecture doesn't match the host architecture, or Docker may choose the wrong platform variant. Consider whether this functionality should be preserved.
| # Preserve legacy behavior: infer Docker platform from image suffix. | |
| # Images ending with "_i386" or "_amd64" automatically get the | |
| # corresponding "--platform" flag. | |
| platform = None | |
| if image.endswith("_i386"): | |
| platform = "linux/386" | |
| elif image.endswith("_amd64"): | |
| platform = "linux/amd64" | |
| if platform is not None: | |
| docker_args += ['--platform', platform] |
src/sc/docker/docker.py
Outdated
| f"xauth add {xauth_line}" | ||
| ] | ||
|
|
||
| except subprocess.CalledProcessError: |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'except' clause does nothing but pass and there is no explanatory comment.
| except subprocess.CalledProcessError: | |
| except subprocess.CalledProcessError: | |
| # If xauth is unavailable or fails, skip adding X11 auth; container may run without GUI access. |
src/sc/docker/docker.py
Outdated
| if not display: | ||
| click.secho("WARNING: No DISPLAY variable set", fg="yellow") | ||
| click.secho("WARNING: X11 not forwarded into docker", fg="yellow") | ||
| docker_args += ["-e", "DISPLAY"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this all be in an else, since there's no point doing it if we didn't get display.
src/sc/docker/docker.py
Outdated
| except subprocess.CalledProcessError: | ||
| pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should actually do something right? If we don't get the line from their xauthority we no point writing the new one inside the container and we need to tell use x11 won't work,
| """Prints and executes the docker run command.""" | ||
| click.secho(" ".join(docker_command), fg='green') | ||
| click.echo() | ||
| os.execvp('docker', docker_command) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless you have deliberate reason to use os.execvp here, I'd stick with a subprocess.run call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to use os.execvp as it gives up the python process for the docker process. Otherwise you have to do lots of funky things to deal with the stream of the subprocess.
TB-1993
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor changes, but otherwise looks good
3b90bdb to
1c1c336
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/sc/docker/docker.py
Outdated
| def _get_remote_images(self, image_ref: str) -> str: | ||
| images = [] | ||
|
|
||
| if local: | ||
| images = list({tag.split(":")[0] for image in self.docker_client.images.list() for tag in image.tags}) | ||
|
|
||
| # If image ref matches a host just get that hosts images. Otherwise get all hosts images. | ||
| host = False | ||
| for registry_url in self.docker_config: | ||
| if image_ref.startswith(registry_url): | ||
| host = registry_url | ||
|
|
||
| if host: | ||
| images = [f"{host}/{image}" for image in self._fetch_images_by_registry(host)] | ||
| else: | ||
| # If image ref matches a host just get that hosts images. Otherwise get all hosts images. | ||
| host = False | ||
| for registry_url in self.docker_config: | ||
| if image_ref.startswith(registry_url): | ||
| host = registry_url | ||
| registry_images = self._fetch_images_by_registry(registry_url) | ||
| images.extend([f"{registry_url}/{image}" for image in registry_images]) | ||
|
|
||
| if host: | ||
| images = [f"{host}/{image}" for image in self._fetch_images_by_registry(host)] | ||
| else: | ||
| for registry_url in self.docker_config: | ||
| registry_images = self._fetch_images_by_registry(registry_url) | ||
| images.extend([f"{registry_url}/{image}" for image in registry_images]) | ||
|
|
||
| return images |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_get_remote_images() is annotated to return str, but it actually returns a list of image strings (images). This breaks type hints and can confuse callers/static analysis; update the return annotation to list[str] (or Sequence[str]) to match the actual value.
| if Path('/opt/coverity').is_symlink() and Path(coverity_dir).exists(): | ||
| docker_args += ["-v", f"{coverity_dir}:{coverity_dir}"] | ||
| bash_commands += [f"export PATH={coverity_dir}:$PATH"] |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Coverity mount uses the resolved target of the /opt/coverity symlink as both the host and container path (e.g. /tools/coverity/...:/tools/coverity/...). This doesn’t ensure /opt/coverity exists inside the container (as described in Issue #17) and can break tooling that expects Coverity at /opt/coverity. Mount the resolved host path to /opt/coverity in the container (and consider adding /opt/coverity/bin to PATH if the intent is to expose executables).
| if Path('/opt/coverity').is_symlink() and Path(coverity_dir).exists(): | |
| docker_args += ["-v", f"{coverity_dir}:{coverity_dir}"] | |
| bash_commands += [f"export PATH={coverity_dir}:$PATH"] | |
| if Path('/opt/coverity').is_symlink() and coverity_dir.exists(): | |
| docker_args += ["-v", f"{coverity_dir}:/opt/coverity"] | |
| bash_commands += ["export PATH=/opt/coverity/bin:$PATH"] |
| hostname = os.getenv("HOSTNAME") or socket.gethostname() | ||
| xauth_line = xauth_line.replace(hostname, container_name, 1) | ||
| except subprocess.CalledProcessError: | ||
| xauth_line = None |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_get_xauth_line() computes xauth_line but never returns it, so callers will always receive None and X11 forwarding will always fall back to _warn_x11_not_forwarded(...). Add an explicit return xauth_line at the end of the function.
| xauth_line = None | |
| xauth_line = None | |
| return xauth_line |
| coverity_dir = Path('/opt/coverity').resolve() | ||
| if Path('/opt/coverity').is_symlink() and Path(coverity_dir).exists(): | ||
| docker_args += ["-v", f"{coverity_dir}:{coverity_dir}"] | ||
| bash_commands += [f"export PATH={coverity_dir}:$PATH"] |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Coverity auto-mount is triggered solely by the presence of the /opt/coverity symlink, but Issue #17 / the PR description mention mounting Coverity "if arg is supplied". There is currently no CLI/run parameter that controls this behavior, so users can’t opt in/out explicitly; either add a sc docker run option to enable the Coverity mount, or adjust the requirement/docs to reflect the auto-detection behavior.
| if self._stdout_connected_to_terminal(): | ||
| docker_args += ['-it'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did we have this before? I don't think this is conditional behaviour. I think SC should always run an interactive shell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# test for TTY input and set the docker option accordingly
test -t 1 && docker_options+=( '-it' )
This is from old sc docker which I copied originally. Researching what the -it flags do I see how we potentially do want to always have at least the -i flag, but I think the -t flag can be dangerous if running in CI?
src/sc/docker/docker.py
Outdated
| if local: | ||
| tags = self._fetch_local_tags(image) | ||
| else: | ||
| username, api_token = self._get_registry_creds_by_url(registry_url) | ||
| tags = self._fetch_remote_tags(image, username, api_token) | ||
|
|
||
| if tag not in tags: | ||
| self._handle_invalid_tag(image, tag, tags) | ||
|
|
||
| if not local: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These three conditional statements test the same condition.
src/sc/docker/docker.py
Outdated
| if host: | ||
| images = [f"{host}/{image}" for image in self._fetch_images_by_registry(host)] | ||
| else: | ||
| # If image ref matches a host just get that hosts images. Otherwise get all hosts images. | ||
| host = False | ||
| for registry_url in self.docker_config: | ||
| if image_ref.startswith(registry_url): | ||
| host = registry_url | ||
| registry_images = self._fetch_images_by_registry(registry_url) | ||
| images.extend([f"{registry_url}/{image}" for image in registry_images]) | ||
|
|
||
| if host: | ||
| images = [f"{host}/{image}" for image in self._fetch_images_by_registry(host)] | ||
| else: | ||
| for registry_url in self.docker_config: | ||
| registry_images = self._fetch_images_by_registry(registry_url) | ||
| images.extend([f"{registry_url}/{image}" for image in registry_images]) | ||
|
|
||
| return images |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These differing behaviours for the function make this quite confusing.
Closes #17
Mount coverity into dockers instead of codesonar