Skip to content

feat: parse "runArgs" using docker/cli's pflag parser - #90

Merged
nozaq merged 3 commits into
mainfrom
claude/runargs-parser-3sljwp
Aug 1, 2026
Merged

feat: parse "runArgs" using docker/cli's pflag parser#90
nozaq merged 3 commits into
mainfrom
claude/runargs-parser-3sljwp

Conversation

@nozaq

@nozaq nozaq commented Aug 1, 2026

Copy link
Copy Markdown
Member

Summary

This change adds a dockerargs package that parses devcontainer.json "runArgs" arrays the same way pflag (docker/cli's argument parser) does, enabling rules to correctly identify flag values regardless of how they're written in the array.

Previously, rules matched array entries directly, which failed when flags consumed following entries (e.g., ["--label", "--cap-drop=ALL"] would incorrectly treat --cap-drop=ALL as the label value). Now rules ask dockerargs for a flag's values, which handles all entry forms pflag recognizes.

Key Changes

  • New dockerargs package (dockerargs/dockerargs.go):

    • Parse() function reads a "runArgs" array as a docker run command line, recognizing all pflag entry forms: --flag=value, -fvalue, -f=value, bare flags, and shorthand runs like -itv
    • Flag type describes a docker run flag (name, shorthand, type, whether it takes a value)
    • Arg type represents one parsed flag occurrence with its value and array index
    • IsTrue() helper for boolean flag values
  • Generated flag table (dockerargs/runflags.go):

    • Complete table of every docker run flag from docker/cli v29.7.1, including hidden and deprecated ones
    • Generated by cmd/dockerflagsgen to stay in sync with docker/cli releases
  • Generator tool (cmd/dockerflagsgen/):

    • Standalone module that builds docker/cli and extracts its registered flags
    • Includes differential tests against pflag to catch parsing divergences
    • Separate module so decolint doesn't depend on docker/cli or pflag
  • Updated rules to use dockerargs.Parse():

    • no_privileged_container, no_cap_add_all, require_cap_drop_all, require_no_new_privileges, no_seccomp_override, no_seccomp_unconfined, no_docker_socket_mount
    • All now correctly find flag values in "runArgs" arrays regardless of entry form
  • Test coverage:

    • Comprehensive tests for Parse() covering all entry forms and edge cases
    • Validation tests for the flag table (uniqueness, sorting)
    • Differential tests comparing against pflag's actual behavior
  • CI integration (ci.yml):

    • New dockerflags job that regenerates the table and fails on diffs, ensuring docker/cli updates are reviewed

Notable Implementation Details

  • Parse() deliberately diverges from pflag in two places (both documented): it reads past the -- terminator and the image name, since a broken "runArgs" array is already in the devcontainer.json and reporting what it says is more useful than falling silent
  • Unknown flags are read both ways (as taking no value and as consuming the next entry) to handle newly-added docker/cli flags gracefully
  • The generator is a separate module with its own go.mod pinning docker/cli, keeping decolint's dependencies clean

https://claude.ai/code/session_01XayP4PXkSJxnTvESxGM3ZR

A devcontainer.json's "runArgs" is spliced into a "docker run" command
line, but the rules read it by matching entries one at a time, which
disagrees with Docker wherever the entry's meaning depends on the flag
before it or on the flag's own arity. ["--label", "--cap-drop=ALL"]
drops no capability, "--privileged=true" is privileged, "--cap-drop=all"
drops every capability, and "-v/var/run/docker.sock:/x" and
["-itv", "/var/run/docker.sock:/x"] both mount the Docker socket.

Add package dockerargs, which reads a "runArgs" array the way pflag —
the parser docker/cli uses — reads an argv, and move the seven rules
that inspect "runArgs" onto it. A rule now asks for a flag's values by
the flag's name, so "-v" and "--volume" are the same flag, while "--net"
stays the separate one docker/cli registers.

The flag table it parses with is generated by cmd/dockerflagsgen, which
builds the command docker/cli builds and reads its flags back out. A
hand-written arity table would go stale the first time Docker adds a
flag, and stale silently. The generator is a module of its own, so
decolint depends on neither docker/cli nor pflag and the repository's
build, tests and linter never reach it; CI regenerates the table and
fails on a diff, so a docker/cli release arrives as a diff to review.

Two tests keep the table honest: a differential test that compares
random argvs against a real pflag.FlagSet built from the table, and a
second spelling-out of the whole table, so regenerating it fails a test
rather than changing which entry a rule reads a value from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XayP4PXkSJxnTvESxGM3ZR
@nozaq nozaq changed the title Parse "runArgs" using docker/cli's pflag parser feat: parse "runArgs" using docker/cli's pflag parser Aug 1, 2026
claude added 2 commits August 1, 2026 08:46
A "go build ./..." run from inside cmd/dockerflagsgen leaves the binary
beside the source, where it was committed by accident. Ignore it, as the
repository already ignores the docgen binary for the same reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XayP4PXkSJxnTvESxGM3ZR
Choosing the destination in one place leaves generate with a single
write and main with a single error path, instead of one per destination.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XayP4PXkSJxnTvESxGM3ZR
@nozaq
nozaq merged commit 37a3e1a into main Aug 1, 2026
12 checks passed
@nozaq
nozaq deleted the claude/runargs-parser-3sljwp branch August 1, 2026 09:06
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.

2 participants