Skip to content

Feature/devcontainers#63

Open
kiuma wants to merge 3 commits into
mainfrom
feature/devcontainers
Open

Feature/devcontainers#63
kiuma wants to merge 3 commits into
mainfrom
feature/devcontainers

Conversation

@kiuma
Copy link
Copy Markdown
Member

@kiuma kiuma commented May 10, 2026

Summary by Sourcery

Add devcontainer awareness and CLI integration to the GNOME Shell Docker extension while deferring heavy checks to avoid blocking shell startup and updating schemas/build packaging.

New Features:

  • Detect devcontainer-related metadata on containers and surface devcontainer name and workspace folder in the menu.
  • Provide menu actions to start and recreate devcontainers via the devcontainer CLI, running through the user’s login shell for version-manager compatibility.
  • Add a helper to open a terminal in a container’s devcontainer workspace folder.

Bug Fixes:

  • Avoid blocking GNOME Shell startup by deferring environment and binary detection like docker/podman checks until actually needed and running some checks asynchronously.

Enhancements:

  • Refine terminal detection and reuse it across docker command execution and devcontainer helpers.
  • Use JSONC-tolerant parsing to extract devcontainer names from config files and metadata.
  • Extend container model to include devcontainer metadata for richer menu rendering.
  • Improve menu labelling by showing devcontainer names beneath container labels in a smaller, italic sub-label.
  • Switch settings schema ID and key prefix to the org.gnome.shell.extensions.easy_docker_containers namespace.

Build:

  • Adjust build script zip exclusions to quote patterns correctly and omit compiled schemas and virtual environment directories from the bundle.

Documentation:

  • Update changelog with the new devcontainer-related menu display behaviour and version entry.

Chores:

  • Remove the docker logging preferences page module from the source tree.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 10, 2026

Reviewer's Guide

Adds devcontainer awareness and controls to the GNOME Shell Docker extension, improves CLI/terminal handling and startup performance, switches the settings schema namespace, and tightens the build and packaging configuration.

Sequence diagram for starting a devcontainer from the GNOME Shell menu

sequenceDiagram
  actor User
  participant TopPanel as GnomeShellTopPanel
  participant DockerMenu
  participant DockerSubMenu
  participant StartItem as DevcontainerStartMenuItem
  participant DockerModule
  participant DevProc as runDevcontainerProcess
  participant DevCLI as devcontainer_CLI
  participant Shell as UserShell

  User ->> TopPanel: Open extension menu
  TopPanel ->> DockerMenu: show()
  DockerMenu ->> DockerModule: getContainers()
  DockerModule -->> DockerMenu: containers with devcontainer info
  DockerMenu ->> DockerSubMenu: create for each container
  User ->> DockerSubMenu: Select devcontainer container
  User ->> StartItem: Activate Start

  StartItem ->> DockerModule: runDevcontainerUp(localFolder)
  DockerModule ->> TopPanel: Main.notify("Devcontainer", "Starting…")
  DockerModule ->> DevProc: runDevcontainerProcess("devcontainer up --workspace-folder localFolder", localFolder)

  DevProc ->> Shell: spawn $SHELL -i -l -c "devcontainer up ... > log 2>&1"
  Shell ->> DevCLI: devcontainer up --workspace-folder localFolder
  DevCLI -->> Shell: exit status
  Shell -->> DevProc: process finished

  alt success
    DevProc -->> DockerModule: resolve(logFile)
    DockerModule ->> DockerModule: delete logFile
    DockerModule ->> TopPanel: Main.notify("Devcontainer", "started")
  else failure
    DevProc -->> DockerModule: reject(logFile)
    DockerModule ->> DockerModule: openTerminalWithLog(logFile, localFolder)
    DockerModule ->> Shell: spawn terminal showing log and shell
  end
Loading

Class diagram for updated Docker menu and devcontainer integration

classDiagram
  class DockerModule {
    <<module>>
    +hasDocker() bool
    +hasPodman() bool
    +hasDevcontainer() bool
    +detectDevcontainerCli() bool
    +openTerminalAtFolder(folderPath string) void
    +runDevcontainerUp(localFolder string) void
    +runDevcontainerRecreate(localFolder string) void
    +isUserInDockerGroup() bool
    +getContainers() Array
  }

  class DockerMenu {
    -settings
    -_timeout
    +_init() void
    +_checkServices() void
    +_checkDockerRunning() void
    +_checkUserInDockerGroup() void
    +_refreshCount() void
  }

  class DockerSubMenu {
    -_parentMenu
    +_init(compose object, devcontainer object, containerName string, containerStatusMessage string, parentMenu object, closePopup function) void
    -_setupDevcontainerName(devcontainerName string) void
    +_getTopMenu() object
  }

  class DockerMenuItem {
    +_init(containerName string, actions Array, icon object, closePopup function) void
  }

  class DevcontainerStartMenuItem {
    +_init(localFolder string, icon object, closePopup function) void
  }

  class DevcontainerRecreateMenuItem {
    +_init(localFolder string, icon object, closePopup function) void
  }

  class DevcontainerInfo {
    +name string
    +localFolder string
    +configFile string
  }

  DockerMenu --> DockerModule : uses
  DockerMenu --> DockerSubMenu : creates
  DockerSubMenu --> DockerMenuItem : creates
  DockerSubMenu --> DevcontainerStartMenuItem : creates
  DockerSubMenu --> DevcontainerRecreateMenuItem : creates
  DockerSubMenu --> DevcontainerInfo : aggregates
  DevcontainerStartMenuItem --> DockerModule : calls runDevcontainerUp()
  DevcontainerRecreateMenuItem --> DockerModule : calls runDevcontainerRecreate()
  DockerModule ..> DevcontainerInfo : returns
Loading

File-Level Changes

Change Details Files
Add devcontainer metadata parsing and actions, including background devcontainer CLI integration and terminal helpers.
  • Introduce JSONC parsing helpers and metadata readers to derive devcontainer names from labels, config files, or metadata blobs.
  • Extract devcontainer info from container labels during inspection and attach it to container objects returned by getContainers().
  • Add terminal detection and helpers to open a terminal in a given folder and to run devcontainer commands via the user’s login shell with async subprocess handling and log file management.
  • Expose runDevcontainerUp and runDevcontainerRecreate helpers that execute devcontainer up commands, notify via GNOME Shell, and surface errors by opening a terminal with the captured logs.
src/docker.js
src/dockerSubMenuMenuItem.js
src/dockerMenuItem.js
icons/docker-devcontainer-info-symbolic.svg
icons/docker-devcontainer-recreate-symbolic.svg
Extend the container submenu UI to show devcontainer details and provide devcontainer-specific actions.
  • Update DockerSubMenu to accept devcontainer info, display a secondary italic devcontainer name label, and add a menu item for the devcontainer workspace folder that opens a terminal.
  • Conditionally render DevcontainerStartMenuItem and DevcontainerRecreateMenuItem instead of the standard Docker start action when a devcontainer local folder is available.
  • Add DevcontainerStartMenuItem and DevcontainerRecreateMenuItem classes that call the new devcontainer helpers and use appropriate icons.
src/dockerSubMenuMenuItem.js
src/dockerMenuItem.js
Improve CLI detection, terminal usage, and docker group checks to be more robust and asynchronous.
  • Replace eager hasDocker/hasPodman flags with lazily-evaluated functions and add a hasDevcontainer helper, deferring PATH scans until after extension enablement.
  • Refactor runCommand to reuse the central terminal detection function and unify terminal selection error handling.
  • Change isUserInDockerGroup from a sync IIFE to an async function using execCommand("groups"), aligning it with other async checks.
src/docker.js
src/dockerMenu.js
Delay initial container refresh and align schema namespace with GNOME Shell extension conventions.
  • Defer the first container count refresh by 5 seconds after extension init to avoid competing with GNOME Shell startup, while still showing a loading placeholder.
  • Update settings key namespace from red.software.systems.easy_docker_containers to org.gnome.shell.extensions.easy_docker_containers and switch the schema file id/path accordingly.
src/dockerMenu.js
schemas/org.gnome.shell.extensions.easy_docker_containers.gschema.xml
Tighten build packaging and documentation around the new devcontainer UI.
  • Adjust build.sh zip invocation to use quoted globs and exclude compiled schemas and virtualenv directories from the extension archive.
  • Add a new changelog entry for version 33 describing the devcontainer UI enhancements.
  • Remove the deprecated docker logging preferences page source file from the project.
build.sh
CHANGELOG.md
src/prefPages/dockerPrefLogging.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • The temporary log path in runDevcontainerProcess uses only the workspace folder basename (e.g. /tmp/devcontainer-foo.log), which can collide across multiple workspaces or concurrent runs; consider including a PID or timestamp in the filename to avoid overwriting logs.
  • Terminal detection logic is duplicated between openTerminalAtFolder and devcontainerTerminalArgv; extracting a shared helper that returns the selected terminal and base argv would reduce repetition and keep behavior in sync.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The temporary log path in runDevcontainerProcess uses only the workspace folder basename (e.g. /tmp/devcontainer-foo.log), which can collide across multiple workspaces or concurrent runs; consider including a PID or timestamp in the filename to avoid overwriting logs.
- Terminal detection logic is duplicated between openTerminalAtFolder and devcontainerTerminalArgv; extracting a shared helper that returns the selected terminal and base argv would reduce repetition and keep behavior in sync.

## Individual Comments

### Comment 1
<location path="src/docker.js" line_range="189-190" />
<code_context>
+    argv = ["ptyxis", "--working-directory", folderPath];
+  } else if (validTerminals["gnome-terminal"]) {
+    argv = ["gnome-terminal", "--working-directory", folderPath];
+  } else if (validTerminals["x-terminal-emulator"]) {
+    argv = ["sh", "-c", `x-terminal-emulator -e sh -c 'cd "${folderPath}"; exec $SHELL'`];
+  } else {
+    logError(
</code_context>
<issue_to_address>
**🚨 issue (security):** Folder paths containing quotes or shell metacharacters will break the x-terminal-emulator command and can be abused.

In the x-terminal-emulator branch, `folderPath` is embedded inside a single-quoted shell string without escaping, so quotes or metacharacters can both break `cd` and enable command injection. Please either:

- Wrap `folderPath` with `GLib.shell_quote(folderPath)` while keeping the surrounding single quotes, or
- Drop the nested `sh -c` and build `argv` as `['x-terminal-emulator', '-e', 'sh', '-c', 'cd ' + GLib.shell_quote(folderPath) + '; exec $SHELL']`.

This ensures arbitrary paths (spaces, quotes, etc.) are passed safely to the shell.
</issue_to_address>

### Comment 2
<location path="src/docker.js" line_range="379" />
<code_context>
+ * @param {String} localFolder
+ */
+const openTerminalWithLog = (logFile, localFolder) => {
+  const shellCmd = `cat "${logFile}"; rm -f "${logFile}"; cd "${localFolder}"; exec $SHELL`;
+  const argv = devcontainerTerminalArgv(shellCmd);
+  if (!argv) return;
</code_context>
<issue_to_address>
**🚨 issue (security):** Unescaped localFolder in the log terminal shell command can break for paths with quotes or special characters.

`openTerminalWithLog` builds a shell command with `localFolder` interpolated directly. If `localFolder` includes quotes or shell metacharacters (e.g. `"`, `;`, `&&`), the `cd` may fail or execute unintended commands. Please wrap `localFolder` with `GLib.shell_quote` when constructing `shellCmd` and adjust the surrounding quotes so it works safely for arbitrary folder names.
</issue_to_address>

### Comment 3
<location path="src/docker.js" line_range="259" />
<code_context>
-  const _userName = GLib.get_user_name();
-  let _userGroups = GLib.ByteArray.toString(
-    GLib.spawn_command_line_sync("groups " + _userName)[1],
+const getUserShell = () => GLib.getenv("SHELL") || "sh";
+
+/**
</code_context>
<issue_to_address>
**nitpick (bug_risk):** The getUserShell JSDoc promises an absolute path, but the implementation may return a bare command name.

If `SHELL` is unset, this returns just `"sh"`, which contradicts the promise of an absolute path. Please either change the fallback to an absolute path (e.g. `/bin/sh`) or relax the JSDoc so it no longer guarantees an absolute path, to keep behavior and documentation aligned.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/docker.js Outdated
Comment thread src/docker.js Outdated
Comment thread src/docker.js
@kiuma kiuma force-pushed the feature/devcontainers branch from 9f6f810 to f66d741 Compare May 10, 2026 17:26
@kiuma
Copy link
Copy Markdown
Member Author

kiuma commented May 10, 2026

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In DockerMenu._checkDockerRunning you still reference Docker.hasPodman as a property instead of calling Docker.hasPodman(), which will now always be truthy and skip the Docker-running check when Podman is absent.
  • The shellCmd strings built for runDevcontainerProcess / runDevcontainerUp / runDevcontainerRecreate interpolate localFolder directly into a quoted argument; consider using GLib.shell_quote(localFolder) (and building the command accordingly) to avoid breakage or injection issues with paths containing quotes or shell metacharacters.
  • The 5‑second startup timeout stored in this._timeout is never cleared on destruction of DockerMenu; it would be safer to track and remove this source in the destroy/disable path to avoid callbacks firing after the extension is disabled.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `DockerMenu._checkDockerRunning` you still reference `Docker.hasPodman` as a property instead of calling `Docker.hasPodman()`, which will now always be truthy and skip the Docker-running check when Podman is absent.
- The `shellCmd` strings built for `runDevcontainerProcess` / `runDevcontainerUp` / `runDevcontainerRecreate` interpolate `localFolder` directly into a quoted argument; consider using `GLib.shell_quote(localFolder)` (and building the command accordingly) to avoid breakage or injection issues with paths containing quotes or shell metacharacters.
- The 5‑second startup timeout stored in `this._timeout` is never cleared on destruction of `DockerMenu`; it would be safer to track and remove this source in the destroy/disable path to avoid callbacks firing after the extension is disabled.

## Individual Comments

### Comment 1
<location path="src/docker.js" line_range="337-346" />
<code_context>
+ * @param {String} localFolder  Workspace folder (used to name the log file)
+ * @return {Promise<string>}
+ */
+const runDevcontainerProcess = (shellCmd, localFolder) => {
+  const folderName = GLib.path_get_basename(localFolder);
+  // Include a timestamp to avoid collisions across concurrent runs or
+  // multiple workspaces that share the same basename.
+  const logFile = `${GLib.get_tmp_dir()}/devcontainer-${folderName}-${Date.now()}.log`;
+  const userShell = getUserShell();
+  const shellFlags = getLoginShellFlags();
+
+  return new Promise((resolve, reject) => {
+    try {
+      const proc = new Gio.Subprocess({
+        argv: [userShell, ...shellFlags, "-c", `${shellCmd} > "${logFile}" 2>&1`],
+        flags: Gio.SubprocessFlags.NONE,
+      });
</code_context>
<issue_to_address>
**issue (bug_risk):** Quote the log file path with GLib.shell_quote instead of embedding it directly in the shell redirection string.

`logFile` is built from `get_tmp_dir()` and the workspace basename, so it can still contain characters (e.g. quotes) that break the current `> "${logFile}" 2>&1` quoting. To harden this, build the command as `... "-c", `${shellCmd} > ${GLib.shell_quote(logFile)} 2>&1`` so the redirection target is shell-escaped regardless of the folder name.
</issue_to_address>

### Comment 2
<location path="src/docker.js" line_range="399-403" />
<code_context>
+ * captured log on failure.
+ * @param {String} localFolder The workspace folder path for the devcontainer
+ */
+export const runDevcontainerUp = (localFolder) => {
+  const folderName = GLib.path_get_basename(localFolder);
+  Main.notify("Devcontainer", `Starting ${folderName}…`);
+
+  runDevcontainerProcess(
+    `devcontainer up --workspace-folder "${localFolder}"`,
+    localFolder
</code_context>
<issue_to_address>
**issue (bug_risk):** Use shell-escaping for `localFolder` in the devcontainer CLI commands to handle paths with quotes or special characters safely.

Both `runDevcontainerUp` and `runDevcontainerRecreate` interpolate `localFolder` into the command as `--workspace-folder "${localFolder}"`. This covers spaces but breaks if the path itself contains a double quote. Since `localFolder` comes from labels/filesystem paths, use `GLib.shell_quote(localFolder)` instead of manual quoting, e.g.:

```js
const quotedFolder = GLib.shell_quote(localFolder);
runDevcontainerProcess(
  `devcontainer up --workspace-folder ${quotedFolder}`,
  localFolder,
);
```

Apply the same pattern to the `--remove-existing-container` variant.
</issue_to_address>

### Comment 3
<location path="CHANGELOG.md" line_range="14" />
<code_context>
+
 ## [32] - 2026-04-27

 Added Gnome 50 support
</code_context>
<issue_to_address>
**nitpick (typo):** Consider using the canonical capitalization "GNOME" instead of "Gnome".

Using "GNOME" here matches the project's official capitalization and aligns with upstream naming.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/docker.js
Comment on lines +337 to +346
const runDevcontainerProcess = (shellCmd, localFolder) => {
const folderName = GLib.path_get_basename(localFolder);
// Include a timestamp to avoid collisions across concurrent runs or
// multiple workspaces that share the same basename.
const logFile = `${GLib.get_tmp_dir()}/devcontainer-${folderName}-${Date.now()}.log`;
const userShell = getUserShell();
const shellFlags = getLoginShellFlags();

return new Promise((resolve, reject) => {
try {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Quote the log file path with GLib.shell_quote instead of embedding it directly in the shell redirection string.

logFile is built from get_tmp_dir() and the workspace basename, so it can still contain characters (e.g. quotes) that break the current > "${logFile}" 2>&1 quoting. To harden this, build the command as ... "-c", ${shellCmd} > ${GLib.shell_quote(logFile)} 2>&1`` so the redirection target is shell-escaped regardless of the folder name.

Comment thread src/docker.js
Comment on lines +399 to +403
export const runDevcontainerUp = (localFolder) => {
const folderName = GLib.path_get_basename(localFolder);
Main.notify("Devcontainer", `Starting ${folderName}…`);

runDevcontainerProcess(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Use shell-escaping for localFolder in the devcontainer CLI commands to handle paths with quotes or special characters safely.

Both runDevcontainerUp and runDevcontainerRecreate interpolate localFolder into the command as --workspace-folder "${localFolder}". This covers spaces but breaks if the path itself contains a double quote. Since localFolder comes from labels/filesystem paths, use GLib.shell_quote(localFolder) instead of manual quoting, e.g.:

const quotedFolder = GLib.shell_quote(localFolder);
runDevcontainerProcess(
  `devcontainer up --workspace-folder ${quotedFolder}`,
  localFolder,
);

Apply the same pattern to the --remove-existing-container variant.

Comment thread CHANGELOG.md

## [32] - 2026-04-27

Added Gnome 50 support
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (typo): Consider using the canonical capitalization "GNOME" instead of "Gnome".

Using "GNOME" here matches the project's official capitalization and aligns with upstream naming.

@kiuma kiuma force-pushed the feature/devcontainers branch from f66d741 to da4c792 Compare May 10, 2026 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant