fix: expose attachment readonly and volume_options in storage:list json
The `storage:list <app> --format json` payload conflated the attachment's `Readonly` flag and its `VolumeOptions` field into a single derived `volume_options` string that rendered as `ro`, `<options>`, or `ro,<options>` depending on which fields were set on the underlying attachment. That shape is fine for the legacy `host:container[:options]` text view but it leaves drift-detection tooling unable to tell whether a `ro` token came from `Attachment.Readonly == true` or from the operator setting `Attachment.VolumeOptions = "ro"` directly. The JSON now exposes `readonly` (boolean) and `volume_options` (string) as separate omitempty keys populated straight from the attachment, and `formatStorageListEntry` combines them at format time for the colon- form text view. `ParseMountPath` was extended in lockstep so callers of the legacy `host:container:opts` form no longer have to special- case the `ro` token themselves.
This commit is contained in:
@@ -82,11 +82,17 @@ func GetBindMountsForDisplay(appName string, phase string) string {
|
||||
}
|
||||
|
||||
// StorageListEntry represents a storage mount entry for JSON output.
|
||||
// Readonly and VolumeOptions mirror the underlying Attachment fields one
|
||||
// for one so external drift-detection tooling can compare against them
|
||||
// directly; the combined "ro,<opts>" colon-form string used by the text
|
||||
// view is derived at format time by formatStorageListEntry rather than
|
||||
// cached on the struct.
|
||||
type StorageListEntry struct {
|
||||
EntryName string `json:"entry_name,omitempty"`
|
||||
HostPath string `json:"host_path"`
|
||||
ContainerPath string `json:"container_path"`
|
||||
VolumeOptions string `json:"volume_options"`
|
||||
Readonly bool `json:"readonly,omitempty"`
|
||||
VolumeOptions string `json:"volume_options,omitempty"`
|
||||
}
|
||||
|
||||
// ListAppMountEntries returns one StorageListEntry per attachment on
|
||||
@@ -117,36 +123,41 @@ func ListAppMountEntries(appName string, phase string) ([]StorageListEntry, erro
|
||||
host = entry.Name
|
||||
}
|
||||
|
||||
options := ""
|
||||
switch {
|
||||
case attachment.Readonly && attachment.VolumeOptions != "":
|
||||
options = "ro," + attachment.VolumeOptions
|
||||
case attachment.Readonly:
|
||||
options = "ro"
|
||||
case attachment.VolumeOptions != "":
|
||||
options = attachment.VolumeOptions
|
||||
}
|
||||
|
||||
rows = append(rows, StorageListEntry{
|
||||
EntryName: entry.Name,
|
||||
HostPath: host,
|
||||
ContainerPath: attachment.ContainerPath,
|
||||
VolumeOptions: options,
|
||||
Readonly: attachment.Readonly,
|
||||
VolumeOptions: attachment.VolumeOptions,
|
||||
})
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
// formatStorageListEntry renders a StorageListEntry into the legacy
|
||||
// host:container[:options] colon form for textual output.
|
||||
// host:container[:options] colon form for textual output. Combines
|
||||
// Readonly and VolumeOptions into a single "ro,<opts>" token to match
|
||||
// the historical shape consumers expect.
|
||||
func formatStorageListEntry(entry StorageListEntry) string {
|
||||
if entry.VolumeOptions == "" {
|
||||
options := ""
|
||||
switch {
|
||||
case entry.Readonly && entry.VolumeOptions != "":
|
||||
options = "ro," + entry.VolumeOptions
|
||||
case entry.Readonly:
|
||||
options = "ro"
|
||||
case entry.VolumeOptions != "":
|
||||
options = entry.VolumeOptions
|
||||
}
|
||||
if options == "" {
|
||||
return fmt.Sprintf("%s:%s", entry.HostPath, entry.ContainerPath)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%s", entry.HostPath, entry.ContainerPath, entry.VolumeOptions)
|
||||
return fmt.Sprintf("%s:%s:%s", entry.HostPath, entry.ContainerPath, options)
|
||||
}
|
||||
|
||||
// ParseMountPath parses a mount path into its components
|
||||
// ParseMountPath parses a mount path into its components. The optional
|
||||
// third colon-separated section is a comma-separated mount-options list;
|
||||
// any "ro" token is hoisted into the Readonly field and the remaining
|
||||
// tokens (preserving order) are rejoined into VolumeOptions.
|
||||
func ParseMountPath(mountPath string) StorageListEntry {
|
||||
parts := strings.SplitN(mountPath, ":", 3)
|
||||
entry := StorageListEntry{}
|
||||
@@ -157,8 +168,16 @@ func ParseMountPath(mountPath string) StorageListEntry {
|
||||
if len(parts) >= 2 {
|
||||
entry.ContainerPath = parts[1]
|
||||
}
|
||||
if len(parts) >= 3 {
|
||||
entry.VolumeOptions = parts[2]
|
||||
if len(parts) >= 3 && parts[2] != "" {
|
||||
remaining := []string{}
|
||||
for _, token := range strings.Split(parts[2], ",") {
|
||||
if token == "ro" {
|
||||
entry.Readonly = true
|
||||
continue
|
||||
}
|
||||
remaining = append(remaining, token)
|
||||
}
|
||||
entry.VolumeOptions = strings.Join(remaining, ",")
|
||||
}
|
||||
|
||||
return entry
|
||||
|
||||
Reference in New Issue
Block a user