Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces basic support for Podman quadlets, allowing their definition within the systemd configuration. The changes include extending the Systemd struct with a Quadlets field, defining a new Quadlet struct, and implementing the translation logic to convert quadlets into Ignition files or symlinks. Comprehensive validation has been added to ensure correct quadlet naming conventions and content sources. The new functionality is well-covered by unit tests, demonstrating handling of basic quadlets, template instances, local content, and error conditions. The implementation is clean and robust for the features introduced.
af44334 to
4f6f4ae
Compare
8cf12e2 to
339567f
Compare
339567f to
4961c66
Compare
prestist
left a comment
There was a problem hiding this comment.
Nice work overall, the approach follows existing patterns well (similar to trees and mount units). The validateNotTooManySources refactor is a clean improvement too.
A few things I noticed -- mostly around the user path semantics and some edge cases in the template instance logic. Also would be good to add a test for quadlet collisions with storage.files entries to make sure the nodeTracker catches those cross-section conflicts.
I see the PR description mentions still needing examples and more tests, so some of this might already be on your radar. Thank you for working on this!
| // We need to handle `foo@bar.container` differently than `foo@.container`, as the former needs to be a symlink to the latter | ||
| var tsFile translate.TranslationSet | ||
| var rFile report.Report | ||
| if isTemplateInstance, templateName := isTemplateInstance(quadlet.Name); isTemplateInstance { |
There was a problem hiding this comment.
nit: the variable isTemplateInstance shadows the function name, its not a bug but its kinda confusing to read, maybe rename it to "isInstance" or something similar.
| func buildQuadletPath(isRoot bool, quadletName string) string { | ||
| const ( | ||
| adminContainersPath = "/etc/containers/systemd" | ||
| userContainersPath = "/etc/containers/systemd/users" |
There was a problem hiding this comment.
Just want to make sure this is intentional -- per the podman docs, /etc/containers/systemd/users/ makes the quadlet run for all non-root users. If the intent is to target a specific user it would need to be /etc/containers/systemd/users/${UID}.
For ignition this is probably the right default, but might be worth calling out in the docs that rootful: false means "all users" and not a specific user.
| if splitIndex == -1 { | ||
| return false, "" | ||
| } | ||
| extensionIndex := strings.LastIndex(name, ".") |
There was a problem hiding this comment.
What happens if name has no . at all (e.g. foo@bar)? LastIndex returns -1 and then splitIndex+1 == extensionIndex would be false, so we'd return true with a malformed template name. I know quadletExtension validation would catch it before we get here but this feels a bit fragile on its own.
| func (rs Quadlet) Validate(c path.ContextPath) (r report.Report) { | ||
| r = validateNotTooManySources(rs.ContentsLocal, rs.Contents, c) | ||
| // Template instances cannot have a content as they are symlinks | ||
| if isTemplateInstance, _ := isTemplateInstance(rs.Name); isTemplateInstance { |
There was a problem hiding this comment.
Hmm, if a template instance has both contents AND contents_local set we would get ErrTooManySystemdSources from validateNotTooManySources and then also ErrTemplateInstanceCannotHaveContents here. Both errors are technically correct but might be worth adding a test case for that specific combo to make sure the report looks right.
Basic sugar for quadlets. Allows for the following syntax:
I still need to add examples for the docs, and to run more tests.
Docs about quadlets can be found here.