Merge pull request #8714 from dokku/8710-storage-report-include-volume-options-for-each-attachment
This commit is contained in:
@@ -242,6 +242,60 @@ You can pass flags which will output only the value of the specific information
|
|||||||
dokku storage:report node-js-app --storage-deploy-mounts
|
dokku storage:report node-js-app --storage-deploy-mounts
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In addition to the aggregated `Storage build/deploy/run mounts:` lines, the report emits one flat dotted key per attachment field, indexed from `1`. The key shape is `--storage-attachment.<index>.<field>` for each of `entry-name`, `host-path`, `container-path`, `phases`, `process-type`, `subpath`, `readonly`, `volume-options`, and `volume-chown`. Fields render as empty strings when unset, and attachments are ordered by lex-sort of the index (so `10` sorts before `2`):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
dokku storage:create node-js-data
|
||||||
|
dokku storage:mount node-js-app node-js-data --container-dir /app/storage --volume-options Z --volume-chown herokuish --volume-subpath uploads
|
||||||
|
dokku storage:report node-js-app
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
=====> node-js-app storage information
|
||||||
|
Storage attachment 1 container path: /app/storage
|
||||||
|
Storage attachment 1 entry name: node-js-data
|
||||||
|
Storage attachment 1 host path: /var/lib/dokku/data/storage/node-js-data
|
||||||
|
Storage attachment 1 phases: deploy,run
|
||||||
|
Storage attachment 1 process type: _default_
|
||||||
|
Storage attachment 1 readonly: false
|
||||||
|
Storage attachment 1 subpath: uploads
|
||||||
|
Storage attachment 1 volume chown: herokuish
|
||||||
|
Storage attachment 1 volume options: Z
|
||||||
|
Storage build mounts:
|
||||||
|
Storage deploy mounts: -v /var/lib/dokku/data/storage/node-js-data:/app/storage:Z
|
||||||
|
Storage run mounts: -v /var/lib/dokku/data/storage/node-js-data:/app/storage:Z
|
||||||
|
```
|
||||||
|
|
||||||
|
The same keys are exposed in JSON output, both in the stripped (`attachment.1.volume-options`) and legacy (`storage-attachment.1.volume-options`) forms:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
dokku storage:report node-js-app --format json | jq '. | with_entries(select(.key | startswith("attachment.")))'
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"attachment.1.container-path": "/app/storage",
|
||||||
|
"attachment.1.entry-name": "node-js-data",
|
||||||
|
"attachment.1.host-path": "/var/lib/dokku/data/storage/node-js-data",
|
||||||
|
"attachment.1.phases": "deploy,run",
|
||||||
|
"attachment.1.process-type": "_default_",
|
||||||
|
"attachment.1.readonly": "false",
|
||||||
|
"attachment.1.subpath": "uploads",
|
||||||
|
"attachment.1.volume-chown": "herokuish",
|
||||||
|
"attachment.1.volume-options": "Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A single attachment field can be fetched directly via the info-flag form:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
dokku storage:report node-js-app --storage-attachment.1.volume-options
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Z
|
||||||
|
```
|
||||||
|
|
||||||
## Use Cases
|
## Use Cases
|
||||||
|
|
||||||
### Sharing storage across deploys
|
### Sharing storage across deploys
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import "github.com/dokku/dokku/plugins/common"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dokku/dokku/plugins/common"
|
||||||
|
)
|
||||||
|
|
||||||
func ReportSingleApp(appName string, format string, infoFlag string) error {
|
func ReportSingleApp(appName string, format string, infoFlag string) error {
|
||||||
if appName != "--global" {
|
if appName != "--global" {
|
||||||
@@ -9,14 +15,26 @@ func ReportSingleApp(appName string, format string, infoFlag string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags map[string]common.ReportFunc
|
flags := map[string]common.ReportFunc{}
|
||||||
if appName == "--global" {
|
if appName != "--global" {
|
||||||
flags = map[string]common.ReportFunc{}
|
flags["--storage-build-mounts"] = reportBuildMounts
|
||||||
} else {
|
flags["--storage-deploy-mounts"] = reportDeployMounts
|
||||||
flags = map[string]common.ReportFunc{
|
flags["--storage-run-mounts"] = reportRunMounts
|
||||||
"--storage-build-mounts": reportBuildMounts,
|
|
||||||
"--storage-deploy-mounts": reportDeployMounts,
|
attachments, err := LoadAttachments(appName)
|
||||||
"--storage-run-mounts": reportRunMounts,
|
if err != nil {
|
||||||
|
common.LogWarn(fmt.Sprintf("Unable to load storage attachments for %q: %s", appName, err))
|
||||||
|
attachments = nil
|
||||||
|
}
|
||||||
|
index := 0
|
||||||
|
for _, attachment := range attachments {
|
||||||
|
entry, err := LoadEntry(attachment.EntryName)
|
||||||
|
if err != nil {
|
||||||
|
common.LogWarn(fmt.Sprintf("Skipping attachment on %q: missing entry %q (%s)", appName, attachment.EntryName, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
registerAttachmentFlags(flags, index, attachment, entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,3 +68,25 @@ func reportDeployMounts(appName string) string {
|
|||||||
func reportRunMounts(appName string) string {
|
func reportRunMounts(appName string) string {
|
||||||
return GetBindMountsForDisplay(appName, "run")
|
return GetBindMountsForDisplay(appName, "run")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// registerAttachmentFlags adds one report flag per Attachment field for the
|
||||||
|
// given 1-based index. Host path is resolved from the referenced Entry
|
||||||
|
// (falling back to entry.Name when the entry has no host path, matching
|
||||||
|
// ListAppMountEntries).
|
||||||
|
func registerAttachmentFlags(flags map[string]common.ReportFunc, index int, att *Attachment, entry *Entry) {
|
||||||
|
prefix := fmt.Sprintf("--storage-attachment.%d.", index)
|
||||||
|
captured := att
|
||||||
|
host := entry.HostPath
|
||||||
|
if host == "" {
|
||||||
|
host = entry.Name
|
||||||
|
}
|
||||||
|
flags[prefix+"entry-name"] = func(string) string { return captured.EntryName }
|
||||||
|
flags[prefix+"host-path"] = func(string) string { return host }
|
||||||
|
flags[prefix+"container-path"] = func(string) string { return captured.ContainerPath }
|
||||||
|
flags[prefix+"phases"] = func(string) string { return strings.Join(captured.Phases, ",") }
|
||||||
|
flags[prefix+"process-type"] = func(string) string { return captured.ProcessType }
|
||||||
|
flags[prefix+"subpath"] = func(string) string { return captured.Subpath }
|
||||||
|
flags[prefix+"readonly"] = func(string) string { return strconv.FormatBool(captured.Readonly) }
|
||||||
|
flags[prefix+"volume-options"] = func(string) string { return captured.VolumeOptions }
|
||||||
|
flags[prefix+"volume-chown"] = func(string) string { return captured.VolumeChown }
|
||||||
|
}
|
||||||
|
|||||||
@@ -197,6 +197,10 @@ teardown() {
|
|||||||
assert_success
|
assert_success
|
||||||
assert_output ""
|
assert_output ""
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '[keys[] | select(startswith(\"attachment.\"))] | length'"
|
||||||
|
assert_success
|
||||||
|
assert_output "0"
|
||||||
|
|
||||||
run /bin/bash -c "dokku storage:mount $TEST_APP /tmp/storage-mount:/mount"
|
run /bin/bash -c "dokku storage:mount $TEST_APP /tmp/storage-mount:/mount"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
@@ -212,10 +216,144 @@ teardown() {
|
|||||||
assert_success
|
assert_success
|
||||||
assert_output ""
|
assert_output ""
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '[keys[] | select(startswith(\"attachment.\"))] | length'"
|
||||||
|
assert_success
|
||||||
|
assert_output "9"
|
||||||
|
|
||||||
run /bin/bash -c "dokku storage:unmount $TEST_APP /tmp/storage-mount:/mount"
|
run /bin/bash -c "dokku storage:unmount $TEST_APP /tmp/storage-mount:/mount"
|
||||||
assert_success
|
assert_success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "(storage:report) emits per-attachment dotted keys" {
|
||||||
|
run /bin/bash -c "dokku storage:create rdmtest-rpt"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt --container-dir /data --phase deploy --phase run --volume-options Z --volume-chown herokuish --volume-subpath uploads"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.entry-name\"'"
|
||||||
|
assert_success
|
||||||
|
assert_output "rdmtest-rpt"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.host-path\"'"
|
||||||
|
assert_success
|
||||||
|
assert_output "$DOKKU_LIB_ROOT/data/storage/rdmtest-rpt"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.container-path\"'"
|
||||||
|
assert_success
|
||||||
|
assert_output "/data"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.phases\"'"
|
||||||
|
assert_success
|
||||||
|
assert_output "deploy,run"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.process-type\"'"
|
||||||
|
assert_success
|
||||||
|
assert_output "_default_"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.subpath\"'"
|
||||||
|
assert_success
|
||||||
|
assert_output "uploads"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.readonly\"'"
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.volume-options\"'"
|
||||||
|
assert_success
|
||||||
|
assert_output "Z"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.volume-chown\"'"
|
||||||
|
assert_success
|
||||||
|
assert_output "herokuish"
|
||||||
|
|
||||||
|
# stdout output gains one line per field; verify a representative subset
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP"
|
||||||
|
assert_success
|
||||||
|
assert_output_contains "Storage attachment 1 entry name"
|
||||||
|
assert_output_contains "rdmtest-rpt" -1
|
||||||
|
assert_output_contains "Storage attachment 1 volume options"
|
||||||
|
assert_output_contains "Storage attachment 1 volume chown"
|
||||||
|
assert_output_contains "herokuish"
|
||||||
|
|
||||||
|
# info-flag lookup returns just the value
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --storage-attachment.1.volume-options"
|
||||||
|
assert_success
|
||||||
|
assert_output "Z"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-rpt --container-dir /data"
|
||||||
|
assert_success
|
||||||
|
run /bin/bash -c "dokku storage:destroy rdmtest-rpt --force"
|
||||||
|
assert_success
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "(storage:report) multiple attachments cluster by index" {
|
||||||
|
run /bin/bash -c "dokku storage:create rdmtest-rpt-a"
|
||||||
|
assert_success
|
||||||
|
run /bin/bash -c "dokku storage:create rdmtest-rpt-b"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt-a --container-dir /data --volume-options Z"
|
||||||
|
assert_success
|
||||||
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt-b --container-dir /cache --volume-options noexec,nosuid"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '[.\"attachment.1.entry-name\", .\"attachment.2.entry-name\"] | sort | join(\"|\")'"
|
||||||
|
assert_success
|
||||||
|
assert_output "rdmtest-rpt-a|rdmtest-rpt-b"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '[.\"attachment.1.volume-options\", .\"attachment.2.volume-options\"] | sort | join(\"|\")'"
|
||||||
|
assert_success
|
||||||
|
assert_output "Z|noexec,nosuid"
|
||||||
|
|
||||||
|
# info-flag lookup against the second attachment
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --storage-attachment.2.entry-name"
|
||||||
|
assert_success
|
||||||
|
assert_output_contains "rdmtest-rpt"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-rpt-a --container-dir /data"
|
||||||
|
assert_success
|
||||||
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-rpt-b --container-dir /cache"
|
||||||
|
assert_success
|
||||||
|
run /bin/bash -c "dokku storage:destroy rdmtest-rpt-a --force"
|
||||||
|
assert_success
|
||||||
|
run /bin/bash -c "dokku storage:destroy rdmtest-rpt-b --force"
|
||||||
|
assert_success
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "(storage:report) degrades gracefully when an attachment entry is missing" {
|
||||||
|
run /bin/bash -c "dokku storage:create rdmtest-rpt-missing"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt-missing --container-dir /data --volume-options Z"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
# delete the entry on disk so LoadEntry fails for the existing attachment
|
||||||
|
run /bin/bash -c "sudo rm -f $DOKKU_LIB_ROOT/data/storage-registry/entries/rdmtest-rpt-missing.json"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
# discard dokku's stderr so the LogWarn line doesn't pollute the jq output capture
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json 2>/dev/null | jq -r '[keys[] | select(startswith(\"attachment.\"))] | length'"
|
||||||
|
assert_success
|
||||||
|
assert_output "0"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json 2>/dev/null | jq -r 'has(\"storage-build-mounts\") and has(\"storage-deploy-mounts\") and has(\"storage-run-mounts\")'"
|
||||||
|
assert_success
|
||||||
|
assert_output "true"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP"
|
||||||
|
assert_success
|
||||||
|
assert_output_contains "Skipping attachment"
|
||||||
|
assert_output_contains "rdmtest-rpt-missing" -1
|
||||||
|
assert_output_contains "Storage build mounts"
|
||||||
|
assert_output_contains "Storage deploy mounts"
|
||||||
|
assert_output_contains "Storage run mounts"
|
||||||
|
|
||||||
|
# clean up the leftover attachment record so subsequent tests don't trip
|
||||||
|
run /bin/bash -c "sudo rm -f $DOKKU_LIB_ROOT/config/storage/$TEST_APP/attachments"
|
||||||
|
assert_success
|
||||||
|
}
|
||||||
|
|
||||||
@test "(storage) storage:create / storage:list-entries / storage:destroy" {
|
@test "(storage) storage:create / storage:list-entries / storage:destroy" {
|
||||||
run /bin/bash -c "dokku storage:create rdmtest-entry"
|
run /bin/bash -c "dokku storage:create rdmtest-entry"
|
||||||
echo "output: $output"
|
echo "output: $output"
|
||||||
@@ -734,4 +872,19 @@ teardown() {
|
|||||||
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"run-mounts\") and has(\"storage-run-mounts\")'"
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"run-mounts\") and has(\"storage-run-mounts\")'"
|
||||||
assert_success
|
assert_success
|
||||||
assert_output "true"
|
assert_output "true"
|
||||||
|
|
||||||
|
# per-attachment dotted keys are emitted in both stripped and legacy forms too
|
||||||
|
run /bin/bash -c "dokku storage:create rdmtest-rpt-keys"
|
||||||
|
assert_success
|
||||||
|
run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt-keys --container-dir /data"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"attachment.1.entry-name\") and has(\"storage-attachment.1.entry-name\")'"
|
||||||
|
assert_success
|
||||||
|
assert_output "true"
|
||||||
|
|
||||||
|
run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-rpt-keys --container-dir /data"
|
||||||
|
assert_success
|
||||||
|
run /bin/bash -c "dokku storage:destroy rdmtest-rpt-keys --force"
|
||||||
|
assert_success
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user