mirror of
https://github.com/dokku/dokku.git
synced 2026-02-24 04:00:36 +01:00
Merge pull request #8264 from dokku/6814-rewrite-storage-plugin-in-golang
Rewrite the storage plugin in golang
This commit is contained in:
@@ -88,6 +88,29 @@ func GetDockerOptionsForPhase(appName string, phase string) ([]string, error) {
|
||||
return options, nil
|
||||
}
|
||||
|
||||
// RemoveDockerOptionFromPhases removes a docker option from specified phases
|
||||
func RemoveDockerOptionFromPhases(appName string, phases []string, option string) error {
|
||||
for _, phase := range phases {
|
||||
options, err := GetDockerOptionsForPhase(appName, phase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newOptions := []string{}
|
||||
for _, opt := range options {
|
||||
if opt != option {
|
||||
newOptions = append(newOptions, opt)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(newOptions)
|
||||
if err = writeDockerOptionsForPhase(appName, phase, newOptions); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSpecifiedDockerOptionsForPhase returns the docker options for the specified phase that are in the desiredOptions list
|
||||
// It expects desiredOptions to be a list of docker options that are in the format "--option"
|
||||
// And will retrieve any lines that start with the desired option
|
||||
|
||||
8
plugins/storage/.gitignore
vendored
Normal file
8
plugins/storage/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/commands
|
||||
/subcommands/*
|
||||
/triggers/*
|
||||
/triggers
|
||||
/storage-*
|
||||
/install
|
||||
/post-*
|
||||
/report
|
||||
7
plugins/storage/Makefile
Normal file
7
plugins/storage/Makefile
Normal file
@@ -0,0 +1,7 @@
|
||||
GOARCH ?= amd64
|
||||
SUBCOMMANDS = subcommands/default subcommands/ensure-directory subcommands/list subcommands/mount subcommands/report subcommands/unmount
|
||||
TRIGGERS = triggers/install triggers/storage-list
|
||||
BUILD = commands subcommands triggers
|
||||
PLUGIN_NAME = storage
|
||||
|
||||
include ../../common.mk
|
||||
@@ -16,10 +16,13 @@ main() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$CHOWN_ID" != "32767" ]] && [[ "$CHOWN_ID" != "1000" ]] && [[ "$CHOWN_ID" != "2000" ]]; then
|
||||
echo " ! Unsupported chown permissions. Supported values: 32767, 1000, 2000"
|
||||
exit 1
|
||||
fi
|
||||
case "$CHOWN_ID" in
|
||||
0 | 1000 | 2000 | 32767 | 165536 | 166536 | 167536 | 198303) ;;
|
||||
*)
|
||||
echo " ! Unsupported chown permissions. Supported values: 0, 1000, 2000, 32767 (and user namespace offset variants)" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
chown -R "$CHOWN_ID:$CHOWN_ID" "${DOKKU_LIB_ROOT}/data/storage/$DIRECTORY"
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
[[ " storage:help help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
|
||||
source "$PLUGIN_AVAILABLE_PATH/storage/help-functions"
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
case "$1" in
|
||||
help | storage:help)
|
||||
cmd-storage-help "$@"
|
||||
;;
|
||||
|
||||
*)
|
||||
exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
|
||||
;;
|
||||
|
||||
esac
|
||||
@@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/docker-options/functions"
|
||||
|
||||
verify_paths() {
|
||||
declare desc="verifies storage paths"
|
||||
local -r passed_path=$1
|
||||
if [[ "$passed_path" == /* ]]; then
|
||||
echo "$passed_path" | grep -qe '^/.*\:/' || dokku_log_fail "Storage path must be two valid paths divided by colon."
|
||||
else
|
||||
echo "$passed_path" | grep -qe '^[a-zA-Z0-9]\{1\}[a-zA-Z0-9_.-]\+\:\/' || dokku_log_fail "Volume name must be two characters or more. Volume name must not contain invalid characters. Storage path must be two valid paths divided by colon."
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
check_if_path_exists() {
|
||||
declare desc="checks if path exists"
|
||||
local -r passed_path=$1
|
||||
local -r phase_file_path=$2
|
||||
[[ -r "$phase_file_path" ]] && grep -qe "^-v $passed_path" "$phase_file_path"
|
||||
}
|
||||
|
||||
get_bind_mounts() {
|
||||
declare desc="strips docker options and prints mounts"
|
||||
local -r phase_file_path=$1
|
||||
if [[ -r "$phase_file_path" ]]; then
|
||||
sed -e '/^-v/!d' -e 's/^-v //' <"$phase_file_path"
|
||||
fi
|
||||
}
|
||||
37
plugins/storage/go.mod
Normal file
37
plugins/storage/go.mod
Normal file
@@ -0,0 +1,37 @@
|
||||
module github.com/dokku/dokku/plugins/storage
|
||||
|
||||
go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
|
||||
github.com/dokku/dokku/plugins/docker-options v0.0.0-00010101000000-000000000000
|
||||
github.com/onsi/gomega v1.38.3
|
||||
github.com/spf13/pflag v1.0.10
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alexellis/go-execute/v2 v2.2.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/melbahja/goph v1.4.0 // indirect
|
||||
github.com/otiai10/copy v1.14.1 // indirect
|
||||
github.com/otiai10/mint v1.6.3 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/sftp v1.13.5 // indirect
|
||||
github.com/ryanuber/columnize v2.1.2+incompatible // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/dokku/dokku/plugins/common => ../common
|
||||
|
||||
replace github.com/dokku/dokku/plugins/docker-options => ../docker-options
|
||||
108
plugins/storage/go.sum
Normal file
108
plugins/storage/go.sum
Normal file
@@ -0,0 +1,108 @@
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/alexellis/go-execute/v2 v2.2.1 h1:4Ye3jiCKQarstODOEmqDSRCqxMHLkC92Bhse743RdOI=
|
||||
github.com/alexellis/go-execute/v2 v2.2.1/go.mod h1:FMdRnUTiFAmYXcv23txrp3VYZfLo24nMpiIneWgKHTQ=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/melbahja/goph v1.4.0 h1:z0PgDbBFe66lRYl3v5dGb9aFgPy0kotuQ37QOwSQFqs=
|
||||
github.com/melbahja/goph v1.4.0/go.mod h1:uG+VfK2Dlhk+O32zFrRlc3kYKTlV6+BtvPWd/kK7U68=
|
||||
github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw=
|
||||
github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
|
||||
github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM=
|
||||
github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
|
||||
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
|
||||
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
|
||||
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
|
||||
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go=
|
||||
github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk=
|
||||
github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
cmd-storage-help() {
|
||||
declare desc="help command"
|
||||
declare CMD="$1"
|
||||
local plugin_name="storage"
|
||||
local plugin_description="Manage mounted volumes"
|
||||
|
||||
if [[ "$CMD" == "${plugin_name}:help" ]]; then
|
||||
echo -e "Usage: dokku ${plugin_name}[:COMMAND]"
|
||||
echo ''
|
||||
echo "$plugin_description"
|
||||
echo ''
|
||||
echo 'Additional commands:'
|
||||
fn-help-content | sort | column -c2 -t -s,
|
||||
elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then
|
||||
fn-help-content
|
||||
else
|
||||
cat <<help_desc
|
||||
$plugin_name, $plugin_description
|
||||
help_desc
|
||||
fi
|
||||
}
|
||||
|
||||
fn-help-content() {
|
||||
declare desc="return help content"
|
||||
cat <<help_content
|
||||
storage:ensure-directory [--chown option] <directory>, Creates a persistent storage directory in the recommended storage path
|
||||
storage:list <app> [--format text|json], List bind mounts for app's container(s) (host:container)
|
||||
storage:mount <app> <host-dir:container-dir>, Create a new bind mount
|
||||
storage:report [<app>] [<flag>], Displays a checks report for one or more apps
|
||||
storage:unmount <app> <host-dir:container-dir>, Remove an existing bind mount
|
||||
help_content
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
trigger-storage-install() {
|
||||
declare desc="storage install trigger"
|
||||
declare trigger="install"
|
||||
|
||||
mkdir -p "${DOKKU_LIB_ROOT}/data/storage"
|
||||
chown "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/storage"
|
||||
|
||||
STORAGE_SUDOERS_FILE="/etc/sudoers.d/dokku-storage"
|
||||
local mode="0440"
|
||||
case "$DOKKU_DISTRO" in
|
||||
arch | debian | raspbian | ubuntu)
|
||||
echo "%dokku ALL=(ALL) NOPASSWD:$PLUGIN_AVAILABLE_PATH/storage/bin/chown-storage-dir *" >"$STORAGE_SUDOERS_FILE"
|
||||
echo "Defaults env_keep += \"DOKKU_LIB_ROOT\"" >>"$STORAGE_SUDOERS_FILE"
|
||||
;;
|
||||
esac
|
||||
|
||||
chmod "$mode" "$STORAGE_SUDOERS_FILE"
|
||||
}
|
||||
|
||||
trigger-storage-install "$@"
|
||||
@@ -1,168 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/docker-options/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/storage/functions"
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
cmd-storage-ensure-directory() {
|
||||
declare desc="creates a persistent storage directory in the recommended storage path"
|
||||
declare cmd="storage:ensure-directory"
|
||||
[[ "$1" == "$cmd" ]] && shift 1
|
||||
declare DIRECTORY CHOWN_FLAG
|
||||
|
||||
CHOWN_FLAG=herokuish
|
||||
skip=false
|
||||
for arg in "$@"; do
|
||||
if [[ "$arg" == "--chown" ]]; then
|
||||
skip=true
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$skip" == "true" ]]; then
|
||||
CHOWN_FLAG="$arg"
|
||||
skip=false
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ -z "$DIRECTORY" ]]; then
|
||||
DIRECTORY="$arg"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$DIRECTORY" ]]; then
|
||||
dokku_log_fail "Please specify a directory to create"
|
||||
fi
|
||||
|
||||
if [[ ! "$DIRECTORY" =~ ^[A-Za-z0-9\\_-]+$ ]]; then
|
||||
dokku_log_fail "Directory can only contain the following set of characters: [A-Za-z0-9_-]"
|
||||
fi
|
||||
|
||||
if [[ "$CHOWN_FLAG" == "herokuish" ]]; then
|
||||
CHOWN_FLAG="32767"
|
||||
elif [[ "$CHOWN_FLAG" == "heroku" ]]; then
|
||||
CHOWN_FLAG="1000"
|
||||
elif [[ "$CHOWN_FLAG" == "packeto" ]]; then
|
||||
CHOWN_FLAG="2000"
|
||||
dokku_log_verbose "Detected deprecated chown flag 'packeto'. Using 'paketo' instead. Please update your configuration."
|
||||
elif [[ "$CHOWN_FLAG" == "paketo" ]]; then
|
||||
CHOWN_FLAG="2000"
|
||||
elif [[ "$CHOWN_FLAG" == "root" ]]; then
|
||||
CHOWN_FLAG="0"
|
||||
elif [[ "$CHOWN_FLAG" == "false" ]]; then
|
||||
CHOWN_FLAG="false"
|
||||
else
|
||||
dokku_log_fail "Unsupported chown permissions"
|
||||
fi
|
||||
|
||||
userns_enabled="$(docker info -f '{{range .SecurityOptions}}{{if eq . "name=userns"}}true{{end}}{{end}}')"
|
||||
if [[ "$userns_enabled" == "true" ]] && [[ "$CHOWN_FLAG" != "false" ]]; then
|
||||
CHOWN_FLAG=$((CHOWN_FLAG + 165536))
|
||||
fi
|
||||
|
||||
local storage_directory="${DOKKU_LIB_ROOT}/data/storage/$DIRECTORY"
|
||||
dokku_log_info1 "Ensuring ${storage_directory} exists"
|
||||
mkdir -p "${storage_directory}"
|
||||
if [[ "$CHOWN_FLAG" != "false" ]]; then
|
||||
dokku_log_verbose_quiet "Setting directory ownership to $CHOWN_FLAG:$CHOWN_FLAG"
|
||||
sudo "$PLUGIN_AVAILABLE_PATH/storage/bin/chown-storage-dir" "$DIRECTORY" "$CHOWN_FLAG"
|
||||
fi
|
||||
|
||||
dokku_log_verbose_quiet "Directory ready for mounting"
|
||||
}
|
||||
|
||||
cmd-storage-report() {
|
||||
declare desc="displays a storage report for one or more apps"
|
||||
declare cmd="storage:report"
|
||||
[[ "$1" == "$cmd" ]] && shift 1
|
||||
declare APP="$1" INFO_FLAG="$2"
|
||||
|
||||
if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then
|
||||
INFO_FLAG="$APP"
|
||||
APP=""
|
||||
fi
|
||||
|
||||
if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then
|
||||
INFO_FLAG="true"
|
||||
fi
|
||||
|
||||
if [[ -z "$APP" ]]; then
|
||||
for app in $(dokku_apps); do
|
||||
cmd-storage-report-single "$app" "$INFO_FLAG" | tee || true
|
||||
done
|
||||
else
|
||||
cmd-storage-report-single "$APP" "$INFO_FLAG"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd-storage-report-single() {
|
||||
declare APP="$1" INFO_FLAG="$2"
|
||||
if [[ "$INFO_FLAG" == "true" ]]; then
|
||||
INFO_FLAG=""
|
||||
fi
|
||||
verify_app_name "$APP"
|
||||
local flag_map=(
|
||||
"--storage-build-mounts: $(fn-storage-bind-mounts "$APP" build)"
|
||||
"--storage-deploy-mounts: $(fn-storage-bind-mounts "$APP" deploy)"
|
||||
"--storage-run-mounts: $(fn-storage-bind-mounts "$APP" run)"
|
||||
)
|
||||
|
||||
if [[ -z "$INFO_FLAG" ]]; then
|
||||
dokku_log_info2_quiet "$APP storage information"
|
||||
for flag in "${flag_map[@]}"; do
|
||||
key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')"
|
||||
dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")"
|
||||
done
|
||||
else
|
||||
local match=false
|
||||
local value_exists=false
|
||||
for flag in "${flag_map[@]}"; do
|
||||
valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)"
|
||||
if [[ "$flag" == "${INFO_FLAG}:"* ]]; then
|
||||
value=${flag#*: }
|
||||
size="${#value}"
|
||||
if [[ "$size" -ne 0 ]]; then
|
||||
echo "$value" && match=true && value_exists=true
|
||||
else
|
||||
match=true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
[[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}"
|
||||
[[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed"
|
||||
fi
|
||||
}
|
||||
|
||||
fn-storage-bind-mounts() {
|
||||
declare APP="$1" PHASE="$2"
|
||||
local PHASE_FILE="$(fn-get-phase-file-path "$APP" "$PHASE")"
|
||||
if [[ -r "$PHASE_FILE" ]]; then
|
||||
sed -e '/^-v/!d' "$PHASE_FILE" | tr '\n' ' '
|
||||
fi
|
||||
}
|
||||
|
||||
cmd-storage-list() {
|
||||
declare desc="List all bound mounts"
|
||||
declare cmd="storage:list"
|
||||
[[ "$1" == "$cmd" ]] && shift 1
|
||||
declare APP="$1" FORMAT_FLAG="$2" FORMAT_FLAG_VALUE="$3"
|
||||
local phase="deploy" format="text"
|
||||
|
||||
if [[ "$FORMAT_FLAG" == "--format" ]]; then
|
||||
if [[ "$FORMAT_FLAG_VALUE" == "json" ]] || [[ "$FORMAT_FLAG_VALUE" == "text" ]]; then
|
||||
format="$FORMAT_FLAG_VALUE"
|
||||
else
|
||||
dokku_log_fail "Invalid --format value specified"
|
||||
fi
|
||||
elif [[ -n "$FORMAT_FLAG" ]]; then
|
||||
dokku_log_fail "Invalid argument specified"
|
||||
fi
|
||||
|
||||
verify_app_name "$APP"
|
||||
if [[ "$FORMAT_FLAG_VALUE" == "text" ]]; then
|
||||
dokku_log_info1_quiet "$APP volume bind-mounts:"
|
||||
plugn trigger storage-list "$APP" "$phase" "$format" | sed "s/^/ /"
|
||||
else
|
||||
plugn trigger storage-list "$APP" "$phase" "$format"
|
||||
fi
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$PLUGIN_AVAILABLE_PATH/storage/internal-functions"
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
cmd-storage-report-single "$@"
|
||||
37
plugins/storage/report.go
Normal file
37
plugins/storage/report.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package storage
|
||||
|
||||
import "github.com/dokku/dokku/plugins/common"
|
||||
|
||||
func ReportSingleApp(appName string, infoFlag string, format string) error {
|
||||
if err := common.VerifyAppName(appName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flags := map[string]common.ReportFunc{
|
||||
"--storage-build-mounts": reportBuildMounts,
|
||||
"--storage-deploy-mounts": reportDeployMounts,
|
||||
"--storage-run-mounts": reportRunMounts,
|
||||
}
|
||||
|
||||
flagKeys := []string{}
|
||||
for flagKey := range flags {
|
||||
flagKeys = append(flagKeys, flagKey)
|
||||
}
|
||||
|
||||
trimPrefix := false
|
||||
uppercaseFirstCharacter := true
|
||||
infoFlags := common.CollectReport(appName, infoFlag, flags)
|
||||
return common.ReportSingleApp("storage", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter)
|
||||
}
|
||||
|
||||
func reportBuildMounts(appName string) string {
|
||||
return GetBindMountsForDisplay(appName, "build")
|
||||
}
|
||||
|
||||
func reportDeployMounts(appName string) string {
|
||||
return GetBindMountsForDisplay(appName, "deploy")
|
||||
}
|
||||
|
||||
func reportRunMounts(appName string) string {
|
||||
return GetBindMountsForDisplay(appName, "run")
|
||||
}
|
||||
60
plugins/storage/src/commands/commands.go
Normal file
60
plugins/storage/src/commands/commands.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
const (
|
||||
helpHeader = `Usage: dokku storage[:COMMAND]
|
||||
|
||||
Manage mounted volumes
|
||||
|
||||
Additional commands:`
|
||||
|
||||
helpContent = `
|
||||
storage:ensure-directory [--chown option] <directory>, Creates a persistent storage directory in the recommended storage path
|
||||
storage:list <app> [--format text|json], List bind mounts for app's container(s) (host:container)
|
||||
storage:mount <app> <host-dir:container-dir>, Create a new bind mount
|
||||
storage:report [<app>] [<flag>], Displays a storage report for one or more apps
|
||||
storage:unmount <app> <host-dir:container-dir>, Remove an existing bind mount`
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
cmd := flag.Arg(0)
|
||||
switch cmd {
|
||||
case "storage":
|
||||
usage()
|
||||
case "storage:help":
|
||||
usage()
|
||||
case "help":
|
||||
result, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: "ps",
|
||||
Args: []string{"-o", "command=", strconv.Itoa(os.Getppid())},
|
||||
})
|
||||
if err == nil && strings.Contains(result.StdoutContents(), "--all") {
|
||||
fmt.Println(helpContent)
|
||||
} else {
|
||||
fmt.Print("\n storage, Manage mounted volumes\n")
|
||||
}
|
||||
default:
|
||||
dokkuNotImplementExitCode, err := strconv.Atoi(os.Getenv("DOKKU_NOT_IMPLEMENTED_EXIT"))
|
||||
if err != nil {
|
||||
fmt.Println("failed to retrieve DOKKU_NOT_IMPLEMENTED_EXIT environment variable")
|
||||
dokkuNotImplementExitCode = 10
|
||||
}
|
||||
os.Exit(dokkuNotImplementExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
common.CommandUsage(helpHeader, helpContent)
|
||||
}
|
||||
62
plugins/storage/src/subcommands/subcommands.go
Normal file
62
plugins/storage/src/subcommands/subcommands.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
"github.com/dokku/dokku/plugins/storage"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func main() {
|
||||
parts := strings.Split(os.Args[0], "/")
|
||||
subcommand := parts[len(parts)-1]
|
||||
|
||||
var err error
|
||||
switch subcommand {
|
||||
case "default":
|
||||
err = storage.CommandHelp()
|
||||
case "ensure-directory":
|
||||
args := flag.NewFlagSet("storage:ensure-directory", flag.ExitOnError)
|
||||
chown := args.String("chown", "herokuish", "--chown: chown option (herokuish, heroku, paketo, root, false)")
|
||||
args.Parse(os.Args[2:])
|
||||
directory := args.Arg(0)
|
||||
err = storage.CommandEnsureDirectory(directory, *chown)
|
||||
case "list":
|
||||
args := flag.NewFlagSet("storage:list", flag.ExitOnError)
|
||||
format := args.String("format", "text", "--format: output format (text, json)")
|
||||
args.Parse(os.Args[2:])
|
||||
appName := args.Arg(0)
|
||||
err = storage.CommandList(appName, *format)
|
||||
case "mount":
|
||||
args := flag.NewFlagSet("storage:mount", flag.ExitOnError)
|
||||
args.Parse(os.Args[2:])
|
||||
appName := args.Arg(0)
|
||||
mountPath := args.Arg(1)
|
||||
err = storage.CommandMount(appName, mountPath)
|
||||
case "report":
|
||||
args := flag.NewFlagSet("scheduler-k3s:report", flag.ExitOnError)
|
||||
format := args.String("format", "stdout", "format: [ stdout | json ]")
|
||||
osArgs, infoFlag, flagErr := common.ParseReportArgs("scheduler-k3s", os.Args[2:])
|
||||
if flagErr == nil {
|
||||
args.Parse(osArgs)
|
||||
appName := args.Arg(0)
|
||||
err = storage.CommandReport(appName, *format, infoFlag)
|
||||
}
|
||||
case "unmount":
|
||||
args := flag.NewFlagSet("storage:unmount", flag.ExitOnError)
|
||||
args.Parse(os.Args[2:])
|
||||
appName := args.Arg(0)
|
||||
mountPath := args.Arg(1)
|
||||
err = storage.CommandUnmount(appName, mountPath)
|
||||
default:
|
||||
err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
common.LogFailWithError(err)
|
||||
}
|
||||
}
|
||||
34
plugins/storage/src/triggers/triggers.go
Normal file
34
plugins/storage/src/triggers/triggers.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
"github.com/dokku/dokku/plugins/storage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
parts := strings.Split(os.Args[0], "/")
|
||||
trigger := parts[len(parts)-1]
|
||||
flag.Parse()
|
||||
|
||||
var err error
|
||||
switch trigger {
|
||||
case "install":
|
||||
err = storage.TriggerInstall()
|
||||
case "storage-list":
|
||||
appName := flag.Arg(0)
|
||||
phase := flag.Arg(1)
|
||||
format := flag.Arg(2)
|
||||
err = storage.TriggerStorageList(appName, phase, format)
|
||||
default:
|
||||
err = fmt.Errorf("Invalid plugin trigger call: %s", trigger)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
common.LogFailWithError(err)
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
source "$PLUGIN_AVAILABLE_PATH/docker-options/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/storage/functions"
|
||||
|
||||
trigger-storage-storage-list() {
|
||||
declare desc="storage storage-list trigger"
|
||||
declare trigger="storage-list"
|
||||
declare APP="$1" PHASE="$2" FORMAT="$3"
|
||||
|
||||
if [[ "$FORMAT" != "json" ]]; then
|
||||
get_bind_mounts "$(fn-get-phase-file-path "$APP" "$PHASE")"
|
||||
else
|
||||
while read -r line; do
|
||||
local host_path="$(awk -F: '{print $1}' <<<"$line")"
|
||||
local container_path="$(awk -F: '{print $2}' <<<"$line")"
|
||||
local volume_options="$(awk -F: '{print $3}' <<<"$line")"
|
||||
jq -n --arg host_path "$host_path" --arg container_path "$container_path" --arg volume_options "$volume_options" '{host_path: $host_path, container_path: $container_path, volume_options: $volume_options}'
|
||||
done < <(get_bind_mounts "$(fn-get-phase-file-path "$APP" "$PHASE")") | jq -M -n '[inputs]'
|
||||
fi
|
||||
}
|
||||
|
||||
trigger-storage-storage-list "$@"
|
||||
130
plugins/storage/storage.go
Normal file
130
plugins/storage/storage.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
dockeroptions "github.com/dokku/dokku/plugins/docker-options"
|
||||
)
|
||||
|
||||
// MountPhases are the phases where storage mounts are applied
|
||||
var MountPhases = []string{"deploy", "run"}
|
||||
|
||||
// VerifyPaths validates the storage mount path format
|
||||
func VerifyPaths(mountPath string) error {
|
||||
if strings.HasPrefix(mountPath, "/") {
|
||||
matched, err := regexp.MatchString(`^/.*:/`, mountPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !matched {
|
||||
return errors.New("Storage path must be two valid paths divided by colon.")
|
||||
}
|
||||
} else {
|
||||
matched, err := regexp.MatchString(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+:/`, mountPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !matched {
|
||||
return errors.New("Volume name must be two characters or more. Volume name must not contain invalid characters. Storage path must be two valid paths divided by colon.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckIfPathExists checks if a mount path exists in the specified phases
|
||||
func CheckIfPathExists(appName string, mountPath string, phases []string) bool {
|
||||
for _, phase := range phases {
|
||||
options, err := dockeroptions.GetDockerOptionsForPhase(appName, phase)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, option := range options {
|
||||
if option == fmt.Sprintf("-v %s", mountPath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetBindMounts returns the bind mounts for an app and phase
|
||||
func GetBindMounts(appName string, phase string) ([]string, error) {
|
||||
mounts := []string{}
|
||||
options, err := dockeroptions.GetDockerOptionsForPhase(appName, phase)
|
||||
if err != nil {
|
||||
return mounts, err
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
if strings.HasPrefix(option, "-v ") {
|
||||
mount := strings.TrimPrefix(option, "-v ")
|
||||
mounts = append(mounts, mount)
|
||||
}
|
||||
}
|
||||
return mounts, nil
|
||||
}
|
||||
|
||||
// GetBindMountsForDisplay returns the bind mounts formatted for display
|
||||
func GetBindMountsForDisplay(appName string, phase string) string {
|
||||
mounts, err := GetBindMounts(appName, phase)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
result := []string{}
|
||||
for _, mount := range mounts {
|
||||
result = append(result, fmt.Sprintf("-v %s", mount))
|
||||
}
|
||||
return strings.Join(result, " ")
|
||||
}
|
||||
|
||||
// StorageListEntry represents a storage mount entry for JSON output
|
||||
type StorageListEntry struct {
|
||||
HostPath string `json:"host_path"`
|
||||
ContainerPath string `json:"container_path"`
|
||||
VolumeOptions string `json:"volume_options"`
|
||||
}
|
||||
|
||||
// ParseMountPath parses a mount path into its components
|
||||
func ParseMountPath(mountPath string) StorageListEntry {
|
||||
parts := strings.SplitN(mountPath, ":", 3)
|
||||
entry := StorageListEntry{}
|
||||
|
||||
if len(parts) >= 1 {
|
||||
entry.HostPath = parts[0]
|
||||
}
|
||||
if len(parts) >= 2 {
|
||||
entry.ContainerPath = parts[1]
|
||||
}
|
||||
if len(parts) >= 3 {
|
||||
entry.VolumeOptions = parts[2]
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
// GetStorageDirectory returns the storage directory path
|
||||
func GetStorageDirectory() string {
|
||||
dokkuLibRoot := common.GetenvWithDefault("DOKKU_LIB_ROOT", "/var/lib/dokku")
|
||||
return fmt.Sprintf("%s/data/storage", dokkuLibRoot)
|
||||
}
|
||||
|
||||
// ValidateDirectoryName validates a storage directory name
|
||||
func ValidateDirectoryName(directory string) error {
|
||||
if directory == "" {
|
||||
return errors.New("Please specify a directory to create")
|
||||
}
|
||||
|
||||
matched, err := regexp.MatchString(`^[A-Za-z0-9_-]+$`, directory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !matched {
|
||||
return errors.New("Directory can only contain the following set of characters: [A-Za-z0-9_-]")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
90
plugins/storage/storage_test.go
Normal file
90
plugins/storage/storage_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestVerifyPathsAbsolutePath(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
Expect(VerifyPaths("/host/path:/container/path")).To(Succeed())
|
||||
Expect(VerifyPaths("/var/lib/dokku/data/storage/test:/app/data")).To(Succeed())
|
||||
}
|
||||
|
||||
func TestVerifyPathsNamedVolume(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
Expect(VerifyPaths("volume_name:/container/path")).To(Succeed())
|
||||
Expect(VerifyPaths("my-volume:/app/data")).To(Succeed())
|
||||
Expect(VerifyPaths("my.volume:/app/data")).To(Succeed())
|
||||
}
|
||||
|
||||
func TestVerifyPathsInvalid(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
err := VerifyPaths("/host/path")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("Storage path must be two valid paths divided by colon"))
|
||||
|
||||
err = VerifyPaths("a:/container")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("Volume name must be two characters or more"))
|
||||
|
||||
err = VerifyPaths("-invalid:/container")
|
||||
Expect(err).To(HaveOccurred())
|
||||
}
|
||||
|
||||
func TestValidateDirectoryName(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
Expect(ValidateDirectoryName("myapp")).To(Succeed())
|
||||
Expect(ValidateDirectoryName("my-app")).To(Succeed())
|
||||
Expect(ValidateDirectoryName("my_app")).To(Succeed())
|
||||
Expect(ValidateDirectoryName("MyApp123")).To(Succeed())
|
||||
}
|
||||
|
||||
func TestValidateDirectoryNameInvalid(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
err := ValidateDirectoryName("")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("Please specify a directory"))
|
||||
|
||||
err = ValidateDirectoryName("@invalid")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("Directory can only contain"))
|
||||
|
||||
err = ValidateDirectoryName("my/app")
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
err = ValidateDirectoryName("my app")
|
||||
Expect(err).To(HaveOccurred())
|
||||
}
|
||||
|
||||
func TestParseMountPath(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
entry := ParseMountPath("/host/path:/container/path")
|
||||
Expect(entry.HostPath).To(Equal("/host/path"))
|
||||
Expect(entry.ContainerPath).To(Equal("/container/path"))
|
||||
Expect(entry.VolumeOptions).To(BeEmpty())
|
||||
|
||||
entry = ParseMountPath("/host/path:/container/path:ro")
|
||||
Expect(entry.HostPath).To(Equal("/host/path"))
|
||||
Expect(entry.ContainerPath).To(Equal("/container/path"))
|
||||
Expect(entry.VolumeOptions).To(Equal("ro"))
|
||||
|
||||
entry = ParseMountPath("volume_name:/container/path")
|
||||
Expect(entry.HostPath).To(Equal("volume_name"))
|
||||
Expect(entry.ContainerPath).To(Equal("/container/path"))
|
||||
Expect(entry.VolumeOptions).To(BeEmpty())
|
||||
}
|
||||
|
||||
func TestGetStorageDirectory(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
dir := GetStorageDirectory()
|
||||
Expect(dir).To(ContainSubstring("data/storage"))
|
||||
}
|
||||
219
plugins/storage/subcommands.go
Normal file
219
plugins/storage/subcommands.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
dockeroptions "github.com/dokku/dokku/plugins/docker-options"
|
||||
)
|
||||
|
||||
const (
|
||||
helpHeader = `Usage: dokku storage[:COMMAND]
|
||||
|
||||
Manage mounted volumes
|
||||
|
||||
Additional commands:`
|
||||
|
||||
helpContent = `
|
||||
storage:ensure-directory [--chown option] <directory>, Creates a persistent storage directory in the recommended storage path
|
||||
storage:list <app> [--format text|json], List bind mounts for app's container(s) (host:container)
|
||||
storage:mount <app> <host-dir:container-dir>, Create a new bind mount
|
||||
storage:report [<app>] [<flag>], Displays a storage report for one or more apps
|
||||
storage:unmount <app> <host-dir:container-dir>, Remove an existing bind mount`
|
||||
)
|
||||
|
||||
// CommandHelp displays help for the storage plugin
|
||||
func CommandHelp() error {
|
||||
common.CommandUsage(helpHeader, helpContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandEnsureDirectory creates a persistent storage directory
|
||||
func CommandEnsureDirectory(directory string, chownFlag string) error {
|
||||
if err := ValidateDirectoryName(directory); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chownID, err := resolveChownID(chownFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
storageDirectory := filepath.Join(GetStorageDirectory(), directory)
|
||||
common.LogInfo1(fmt.Sprintf("Ensuring %s exists", storageDirectory))
|
||||
|
||||
if err := os.MkdirAll(storageDirectory, 0755); err != nil {
|
||||
return fmt.Errorf("Unable to create directory: %s", err.Error())
|
||||
}
|
||||
|
||||
if chownID != "false" {
|
||||
common.LogVerboseQuiet(fmt.Sprintf("Setting directory ownership to %s:%s", chownID, chownID))
|
||||
|
||||
pluginPath := common.MustGetEnv("PLUGIN_AVAILABLE_PATH")
|
||||
chownScript := filepath.Join(pluginPath, "storage", "bin", "chown-storage-dir")
|
||||
|
||||
result, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: "sudo",
|
||||
Args: []string{chownScript, directory, chownID},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to set directory ownership: %s", err.Error())
|
||||
}
|
||||
if result.ExitCode != 0 {
|
||||
return fmt.Errorf("Unable to set directory ownership: %s", result.StderrContents())
|
||||
}
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet("Directory ready for mounting")
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveChownID converts a chown flag value to a numeric UID
|
||||
func resolveChownID(chownFlag string) (string, error) {
|
||||
var chownID string
|
||||
|
||||
switch chownFlag {
|
||||
case "herokuish":
|
||||
chownID = "32767"
|
||||
case "heroku":
|
||||
chownID = "1000"
|
||||
case "packeto":
|
||||
common.LogVerbose("Detected deprecated chown flag 'packeto'. Using 'paketo' instead. Please update your configuration.")
|
||||
chownID = "2000"
|
||||
case "paketo":
|
||||
chownID = "2000"
|
||||
case "root":
|
||||
chownID = "0"
|
||||
case "false":
|
||||
return "false", nil
|
||||
default:
|
||||
return "", errors.New("Unsupported chown permissions")
|
||||
}
|
||||
|
||||
userns, err := isUserNamespacesEnabled()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if userns && chownID != "false" {
|
||||
uid := 0
|
||||
fmt.Sscanf(chownID, "%d", &uid)
|
||||
uid += 165536
|
||||
chownID = fmt.Sprintf("%d", uid)
|
||||
}
|
||||
|
||||
return chownID, nil
|
||||
}
|
||||
|
||||
// isUserNamespacesEnabled checks if Docker user namespaces are enabled
|
||||
func isUserNamespacesEnabled() (bool, error) {
|
||||
result, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: common.DockerBin(),
|
||||
Args: []string{"info", "-f", "{{range .SecurityOptions}}{{if eq . \"name=userns\"}}true{{end}}{{end}}"},
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strings.TrimSpace(result.StdoutContents()) == "true", nil
|
||||
}
|
||||
|
||||
// CommandMount creates a new bind mount for an app
|
||||
func CommandMount(appName string, mountPath string) error {
|
||||
if err := common.VerifyAppName(appName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := VerifyPaths(mountPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if CheckIfPathExists(appName, mountPath, MountPhases) {
|
||||
return errors.New("Mount path already exists.")
|
||||
}
|
||||
|
||||
return dockeroptions.AddDockerOptionToPhases(appName, MountPhases, fmt.Sprintf("-v %s", mountPath))
|
||||
}
|
||||
|
||||
// CommandUnmount removes an existing bind mount from an app
|
||||
func CommandUnmount(appName string, mountPath string) error {
|
||||
if err := common.VerifyAppName(appName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := VerifyPaths(mountPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !CheckIfPathExists(appName, mountPath, MountPhases) {
|
||||
return errors.New("Mount path does not exist.")
|
||||
}
|
||||
|
||||
return dockeroptions.RemoveDockerOptionFromPhases(appName, MountPhases, fmt.Sprintf("-v %s", mountPath))
|
||||
}
|
||||
|
||||
// CommandList lists all bind mounts for an app
|
||||
func CommandList(appName string, format string) error {
|
||||
if err := common.VerifyAppName(appName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if format != "text" && format != "json" {
|
||||
return errors.New("Invalid --format value specified")
|
||||
}
|
||||
|
||||
if format == "text" {
|
||||
common.LogInfo1Quiet(fmt.Sprintf("%s volume bind-mounts:", appName))
|
||||
}
|
||||
|
||||
results, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
Trigger: "storage-list",
|
||||
Args: []string{appName, "deploy", format},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output := results.StdoutContents()
|
||||
if format == "text" && output != "" {
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
for _, line := range lines {
|
||||
if line != "" {
|
||||
if os.Getenv("DOKKU_QUIET_OUTPUT") != "" {
|
||||
fmt.Println(line)
|
||||
} else {
|
||||
common.LogVerbose(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if output != "" {
|
||||
fmt.Println(output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandReport displays a storage report for one or more apps
|
||||
func CommandReport(appName string, format string, infoFlag string) error {
|
||||
if appName == "" {
|
||||
apps, err := common.DokkuApps()
|
||||
if err != nil {
|
||||
if errors.Is(err, common.NoAppsExist) {
|
||||
common.LogWarn(err.Error())
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
for _, app := range apps {
|
||||
if err := ReportSingleApp(app, format, infoFlag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return ReportSingleApp(appName, format, infoFlag)
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
source "$PLUGIN_AVAILABLE_PATH/storage/help-functions"
|
||||
|
||||
cmd-storage-help "storage:help"
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
source "$PLUGIN_AVAILABLE_PATH/storage/internal-functions"
|
||||
|
||||
cmd-storage-ensure-directory "$@"
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
source "$PLUGIN_AVAILABLE_PATH/storage/internal-functions"
|
||||
|
||||
cmd-storage-list "$@"
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/storage/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/docker-options/functions"
|
||||
|
||||
cmd-storage-mount() {
|
||||
declare desc="Add bind-mount, redeploy app if running"
|
||||
declare cmd="storage:mount"
|
||||
[[ "$1" == "$cmd" ]] && shift 1
|
||||
declare APP="$1" MOUNT_PATH="$2"
|
||||
local passed_phases=(deploy run)
|
||||
|
||||
verify_app_name "$APP"
|
||||
verify_paths "$MOUNT_PATH"
|
||||
check_if_path_exists "$MOUNT_PATH" "$(get_phase_file_path "${passed_phases[@]}")" && dokku_log_fail "Mount path already exists."
|
||||
add_passed_docker_option passed_phases[@] "-v $MOUNT_PATH"
|
||||
}
|
||||
|
||||
cmd-storage-mount "$@"
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$PLUGIN_AVAILABLE_PATH/storage/internal-functions"
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
cmd-storage-report "$@"
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/storage/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/docker-options/functions"
|
||||
|
||||
cmd-storage-unmount() {
|
||||
declare desc="Remove bind-mount, restart app if running"
|
||||
declare cmd="storage:unmount"
|
||||
[[ "$1" == "$cmd" ]] && shift 1
|
||||
declare APP="$1" MOUNT_PATH="$2"
|
||||
local passed_phases=(deploy run)
|
||||
|
||||
verify_app_name "$APP"
|
||||
verify_paths "$MOUNT_PATH"
|
||||
check_if_path_exists "$MOUNT_PATH" "$(get_phase_file_path "${passed_phases[@]}")" || dokku_log_fail "Mount path does not exist."
|
||||
remove_passed_docker_option passed_phases[@] "-v $MOUNT_PATH"
|
||||
}
|
||||
|
||||
cmd-storage-unmount "$@"
|
||||
93
plugins/storage/triggers.go
Normal file
93
plugins/storage/triggers.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
// TriggerInstall sets up the storage plugin on installation
|
||||
func TriggerInstall() error {
|
||||
storageDir := GetStorageDirectory()
|
||||
|
||||
if err := os.MkdirAll(storageDir, 0755); err != nil {
|
||||
return fmt.Errorf("Unable to create storage directory: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := common.SetPermissions(common.SetPermissionInput{
|
||||
Filename: storageDir,
|
||||
Mode: 0755,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("Unable to set storage directory permissions: %s", err.Error())
|
||||
}
|
||||
|
||||
distro := detectDistro()
|
||||
if distro == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
pluginPath := common.MustGetEnv("PLUGIN_AVAILABLE_PATH")
|
||||
chownScript := filepath.Join(pluginPath, "storage", "bin", "chown-storage-dir")
|
||||
|
||||
sudoersFile := "/etc/sudoers.d/dokku-storage"
|
||||
content := fmt.Sprintf("%%dokku ALL=(ALL) NOPASSWD:%s *\n", chownScript)
|
||||
content += "Defaults env_keep += \"DOKKU_LIB_ROOT\"\n"
|
||||
|
||||
if err := os.WriteFile(sudoersFile, []byte(content), 0440); err != nil {
|
||||
return fmt.Errorf("Unable to write sudoers file: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectDistro returns the Linux distribution name
|
||||
func detectDistro() string {
|
||||
if runtime.GOOS != "linux" {
|
||||
return ""
|
||||
}
|
||||
|
||||
distro := os.Getenv("DOKKU_DISTRO")
|
||||
if distro != "" {
|
||||
return distro
|
||||
}
|
||||
|
||||
if common.FileExists("/etc/debian_version") {
|
||||
return "debian"
|
||||
}
|
||||
if common.FileExists("/etc/arch-release") {
|
||||
return "arch"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// TriggerStorageList outputs storage mounts for an app
|
||||
func TriggerStorageList(appName string, phase string, format string) error {
|
||||
mounts, err := GetBindMounts(appName, phase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if format == "json" {
|
||||
entries := []StorageListEntry{}
|
||||
for _, mount := range mounts {
|
||||
entries = append(entries, ParseMountPath(mount))
|
||||
}
|
||||
|
||||
output, err := json.Marshal(entries)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(output))
|
||||
} else {
|
||||
for _, mount := range mounts {
|
||||
fmt.Println(mount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user