Skip to content

cmdline: add support for loading config from a local device#2230

Open
atd9876 wants to merge 3 commits into
coreos:mainfrom
atd9876:add-metal-config-drive
Open

cmdline: add support for loading config from a local device#2230
atd9876 wants to merge 3 commits into
coreos:mainfrom
atd9876:add-metal-config-drive

Conversation

@atd9876

@atd9876 atd9876 commented May 8, 2026

Copy link
Copy Markdown

Summary

Add support for loading Ignition configuration from a local device
specified via kernel command-line flags ignition.config.device and
ignition.config.path. This enables bare-metal and air-gapped
environments to provide Ignition configs on a labeled disk partition
(e.g., a config drive) without requiring network access.

Acknowledgments

The initial device-based config loading implementation (commit 6c250bc)
was authored by Jan Larwig (@tuunit). This PR builds on that work with
tests, documentation, and robustness improvements.

Changes

  • Device-based config loading: parseCmdline now recognizes
    ignition.config.device and ignition.config.path flags.
    fetchConfig uses both to mount the labeled device read-only and
    read the config file at the specified path.
  • tryMounting function: Waits for the labeled device to appear
    (with a 30-second timeout), mounts it read-only into a temporary
    directory, reads the config, and unmounts on return.
  • Robustness improvements to parseCmdline:
    • Use strings.Fields instead of strings.Split to correctly
      handle tabs, multiple spaces, and trailing newlines.
    • Rename local url variable to parsedURL to avoid shadowing
      the net/url import.
    • Use errors.Is(err, context.DeadlineExceeded) instead of ==
      to correctly match wrapped errors.
  • Tests: Add unit tests covering URL parsing, device+path parsing,
    partial flags, empty flags, invalid URLs, precedence, fileExists,
    and error handling in test helpers.
  • Documentation: Update docs for the new command-line flags.

Fixes #2207

Summary by CodeRabbit

  • New Features

    • Added support for loading Ignition configuration from a locally attached, labeled device using kernel command-line options.
    • Remote URL configuration remains supported and takes precedence when both methods are specified.
    • The device label and configuration path must be provided together.
  • Bug Fixes

    • Added validation and clearer errors for missing devices, files, or incomplete configuration options.
  • Documentation

    • Updated release notes and supported platform documentation with the new configuration method.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for loading Ignition configurations from a local device on bare metal platforms via the ignition.config.device and ignition.config.path kernel parameters. The implementation includes documentation updates, a refactored command-line parser, and logic to mount the specified device and read the configuration file. Review feedback identifies a potential logic error where returning configErrors.ErrEmpty might cause a fatal failure instead of a fallback when a device is missing, and a path traversal vulnerability when accessing the configuration file on the mounted device.

Comment thread internal/providers/cmdline/cmdline.go
Comment thread internal/providers/cmdline/cmdline.go Outdated
@atd9876
atd9876 force-pushed the add-metal-config-drive branch from bab4a01 to a13e77b Compare May 8, 2026 12:25

@prestist prestist left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is looking good overall, thank you for working on this! Some small comments.

I think we could use some integration tests, I would add a blackbox test (in tests/positive/) that sets up a labeled disk image with a config file and boots with ignition.config.device=LABEL ignition.config.path=/config.ign, wdyt?

Comment thread internal/providers/cmdline/cmdline.go Outdated
Comment thread internal/providers/cmdline/cmdline.go Outdated
Comment thread internal/providers/cmdline/cmdline.go

@prestist prestist left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just two comments, Im not sure how I feel about them. What are your thoughts?

Comment thread internal/providers/cmdline/cmdline.go
Comment thread internal/providers/cmdline/cmdline.go Outdated
@prestist

prestist commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the updates in 9a073d5 -- I've re-reviewed and the error handling changes look good. Incomplete flags, missing config, and device timeout all correctly return hard errors now instead of falling through silently. The negative blackbox tests are a nice addition too.
I'm fine with the 30s timeout -- laenion's data from SUSE backs that up.

A couple of things I'd like addressed before we merge:
configMinVersion in the tests should be "3.0.0" -- this feature is a provider behavior change, not a spec change. Setting it to 3.4.0 means the tests only run against spec versions >= 3.4.0 when there's no reason for that restriction.
Device label should be validated -- the config file path is properly sanitized with filepath.Clean(filepath.Join("/", ...)) which is great, but the device label from the kernel cmdline is passed straight into filepath.Join(distro.DiskByLabelDir(), opts.DeviceLabel) without the same treatment. Something like rejecting labels containing / or .. would keep it consistent.
A few smaller things (not blocking, but worth considering):

  • If both ignition.config.url and ignition.config.device/ignition.config.path are set, the URL silently wins. Would be good to log a warning so the user knows their device flags are being ignored.
  • Could use a negative test for the "only one of device/path set" error case -- you cover config-not-found and device-not-found but not this one.
  • The distro.go change making KernelCmdlinePath() env-overridable is fine (follows the existing pattern for SystemConfigDir etc.) but ideally should be its own commit since it affects all callers.

Lastly the "Require release note" CI check is failing -- can you add a release note?

@atd9876

atd9876 commented Jun 9, 2026

Copy link
Copy Markdown
Author

I will fix those issues.

Re the

  • negative test for the "only one of device/path set" error case - I felt that the unit tests covered that but I will add one.

@atd9876
atd9876 force-pushed the add-metal-config-drive branch from 9a073d5 to 0861cdd Compare June 10, 2026 08:41
@prestist

Copy link
Copy Markdown
Collaborator

@atd9876 Okay from my perspective this is lgtm, just need to rebase and resolve any commits to get the CI working :)

@atd9876
atd9876 force-pushed the add-metal-config-drive branch from 0861cdd to 06a1302 Compare July 3, 2026 12:20
atd9876 added 2 commits July 7, 2026 09:38
Follow the existing pattern used by SystemConfigDir and other distro
functions, allowing KernelCmdlinePath to be overridden via the
IGNITION_KERNEL_CMDLINE_PATH environment variable. This enables
blackbox tests to inject a fake kernel cmdline.

Signed-off-by: Andrew Dodds <andrew.dodds@sap.com>
Add support for loading Ignition config from a labeled block device via
the ignition.config.device and ignition.config.path kernel command-line
arguments. Both must be specified together.

Error handling:
- Return hard error if only one of device/path is specified
- Return hard error if the device does not appear within 30s timeout
- Return hard error if the config file is not found on the device

Includes unit tests for parseCmdline and blackbox integration tests
covering both positive (successful fetch) and negative (missing device,
missing file, incomplete options) scenarios.

Signed-off-by: Andrew Dodds <andrew.dodds@sap.com>
@atd9876
atd9876 force-pushed the add-metal-config-drive branch from 06a1302 to feb4f08 Compare July 7, 2026 08:38
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Binary size report (bin/amd64/ignition)

Size
Base (main) 21MiB
PR (#2230) 21MiB
Delta +16KiB (0.07%)

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Ignition’s cmdline provider now supports configuration files from labeled local devices using ignition.config.device and ignition.config.path, while retaining URL support. Parsing, mounting, error handling, environment overrides, documentation, and positive and negative tests were added.

Changes

Kernel Command-Line Device Configuration

Layer / File(s) Summary
Cmdline parsing and source selection
internal/providers/cmdline/cmdline.go, internal/providers/cmdline/cmdline_test.go
Parses URL, device, and path parameters, applies URL precedence, validates paired device options, and tests parsing and label validation.
Labeled-device mounting and config retrieval
internal/providers/cmdline/cmdline.go, internal/distro/distro.go, tests/positive/cmdline/*, tests/negative/cmdline/*, tests/registry/registry.go, tests/blackbox_test.go
Waits for and mounts labeled devices read-only, reads configuration files, supports an environment-selected cmdline path, and registers positive and negative integration scenarios.
Configuration method documentation
docs/release-notes.md, docs/supported-platforms.md
Documents local configuration loading with paired device and path kernel parameters.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant KernelCmdline
  participant CmdlineProvider
  participant LabeledDevice
  participant utilParseConfig
  KernelCmdline->>CmdlineProvider: provide ignition.config.device and ignition.config.path
  CmdlineProvider->>LabeledDevice: wait for label and mount read-only
  LabeledDevice-->>CmdlineProvider: return config file contents
  CmdlineProvider->>utilParseConfig: parse configuration contents
Loading

Suggested reviewers: prestist

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding local-device config loading support.
Linked Issues check ✅ Passed The changes implement bare-metal config-drive support from attached block devices, alongside URL config and graceful failures, matching #2207.
Out of Scope Changes check ✅ Passed The added docs, tests, and kernel cmdline path override are directly supporting the new device-loading feature and do not appear unrelated.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/supported-platforms.md`:
- Line 24: Update the Bare Metal configuration documentation to state that when
ignition.config.url and the ignition.config.device/path pair are all supplied,
ignition.config.url takes precedence and the device/path configuration is
ignored.

In `@internal/providers/cmdline/cmdline.go`:
- Around line 178-221: Update tryMounting so the entire device retrieval flow,
including mounting and reading the config, is retried until the context expires.
Use exec.CommandContext for the mount command so a hanging mount is canceled by
ctx, and retry transient mount or os.ReadFile failures rather than returning
immediately; preserve successful reads and the existing not-found behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Enterprise

Run ID: becb678f-b985-4ac4-8a91-cc0c49628aca

📥 Commits

Reviewing files that changed from the base of the PR and between 4e9026c and f862e91.

📒 Files selected for processing (9)
  • docs/release-notes.md
  • docs/supported-platforms.md
  • internal/distro/distro.go
  • internal/providers/cmdline/cmdline.go
  • internal/providers/cmdline/cmdline_test.go
  • tests/blackbox_test.go
  • tests/negative/cmdline/cmdline.go
  • tests/positive/cmdline/cmdline.go
  • tests/registry/registry.go

* [IBM Cloud] (`ibmcloud`) - Ignition will read its configuration from the instance userdata. Cloud SSH keys are handled separately.
* [KubeVirt] (`kubevirt`) - Ignition will read its configuration from the instance userdata via `cloudInitConfigDrive` or `cloudInitNoCloud`. Cloud SSH keys are handled separately.
* Bare Metal (`metal`) - Use the `ignition.config.url` kernel parameter to provide a URL to the configuration. The URL can use the `http://`, `https://`, `tftp://`, `s3://`, `arn:`, or `gs://` schemes to specify a remote config.
* Bare Metal (`metal`) - Use the `ignition.config.url` kernel parameter to provide a URL to the configuration. The URL can use the `http://`, `https://`, `tftp://`, `s3://`, `arn:`, or `gs://` schemes to specify a remote config. Alternatively, use `ignition.config.device` (a disk-by-label name, e.g. `CONFIG`) and `ignition.config.path` (the path to the config file on that device, e.g. `/ignition/config.ign`) to load the configuration from a locally attached device. Both parameters must be provided together.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document URL precedence when both sources are supplied.

The provider gives ignition.config.url precedence and ignores the device/path pair when all are present, as implemented in internal/providers/cmdline/cmdline.go:77-80. State this explicitly so operators do not assume the device configuration will be used.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/supported-platforms.md` at line 24, Update the Bare Metal configuration
documentation to state that when ignition.config.url and the
ignition.config.device/path pair are all supplied, ignition.config.url takes
precedence and the device/path configuration is ignored.

Comment on lines +178 to +221
func tryMounting(logger *log.Logger, ctx context.Context, opts *cmdlineOpts) ([]byte, error) {
device := filepath.Join(distro.DiskByLabelDir(), opts.DeviceLabel)
for !fileExists(device) {
logger.Debug("disk (%q) not found. Waiting...", device)
select {
case <-time.After(time.Second):
case <-ctx.Done():
return nil, ctx.Err()
}
}

if len(parts) == 2 {
url = parts[1]
logger.Debug("creating temporary mount point")
mnt, err := os.MkdirTemp("", "ignition-config")
if err != nil {
return nil, fmt.Errorf("failed to create temp directory: %v", err)
}
defer func() {
if err := os.Remove(mnt); err != nil {
logger.Err("failed to remove temporary mount point %q: %v", mnt, err)
}
}()

cmd := exec.Command(distro.MountCmd(), "-o", "ro", "-t", "auto", device, mnt)
if _, err := logger.LogCmd(cmd, "mounting disk"); err != nil {
return nil, err
}
defer func() {
_ = logger.LogOp(
func() error {
return ut.UmountPath(mnt)
},
"unmounting %q at %q", device, mnt,
)
}()

configPath := filepath.Join(mnt, filepath.Clean(filepath.Join("/", opts.UserDataPath)))
if !fileExists(configPath) {
logger.Debug("config file %q not found on device %q", opts.UserDataPath, opts.DeviceLabel)
return nil, nil
}

contents, err := os.ReadFile(configPath)
if err != nil {
return nil, err

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Bound and retry the complete device retrieval operation.

The context only governs the device-existence loop. mount can hang indefinitely because it uses exec.Command, and transient mount/read failures immediately abort instead of retrying. Use exec.CommandContext and retry the mount/read attempt until the context expires.

As per coding guidelines, “Platform providers must retry config fetches.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/providers/cmdline/cmdline.go` around lines 178 - 221, Update
tryMounting so the entire device retrieval flow, including mounting and reading
the config, is retried until the context expires. Use exec.CommandContext for
the mount command so a hanging mount is canceled by ctx, and retry transient
mount or os.ReadFile failures rather than returning immediately; preserve
successful reads and the existing not-found behavior.

Source: Coding guidelines

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The exec.CommandContext point is valid -- if mount hangs (e.g. unresponsive block device), the 30s context only covers the device-existence loop, not the mount itself. Switching to exec.CommandContext(ctx, ...) at line 200 is a small change and would prevent an indefinite hang.

@atd9876 I think this would be a worthwhile fix, everything else is minor. If you want I can do this so we can get this merged.

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.

metal: add config drive support for bare metal provisioning

3 participants