feat: port vhost proxy :report subcommands to golang

The bash :report implementations for the caddy, haproxy, openresty, and traefik proxy plugins are replaced with a compiled golang binary that collects report keys in parallel and marshals json directly. The traefik dns-provider values keep their masking behaviour, remaining hidden in the default stdout report while surfacing for --format json or an explicit flag query. These four plugins previously had no unit tests, so a bats suite covering the report matrix is added for each.
This commit is contained in:
Jose Diaz-Gonzalez
2026-07-07 06:29:01 -04:00
parent f9db9583a1
commit 5faabea3d4
49 changed files with 1853 additions and 605 deletions

View File

@@ -11,13 +11,16 @@ use (
./plugins/builder-pack
./plugins/builder-railpack
./plugins/buildpacks
./plugins/caddy-vhosts
./plugins/common
./plugins/config
./plugins/cron
./plugins/docker-options
./plugins/haproxy-vhosts
./plugins/logs
./plugins/network
./plugins/nginx-vhosts
./plugins/openresty-vhosts
./plugins/ports
./plugins/proxy
./plugins/ps
@@ -28,4 +31,5 @@ use (
./plugins/scheduler-docker-local
./plugins/scheduler-k3s
./plugins/storage
./plugins/traefik-vhosts
)

4
plugins/caddy-vhosts/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/report
/report-subcommand
/triggers
/subcommands/report

View File

@@ -0,0 +1,13 @@
GOARCH ?= amd64
TRIGGERS = triggers/report
BUILD = report-subcommand triggers
PLUGIN_NAME = caddy-vhosts
clean-report-subcommand:
rm -rf report-subcommand
report-subcommand: clean-report-subcommand src/subcommands/subcommands.go
GOARCH=$(GOARCH) go build -mod=readonly -ldflags="-s -w" $(GO_ARGS) -o report-subcommand src/subcommands/subcommands.go
ln -sf ../report-subcommand subcommands/report
include ../../common.mk

107
plugins/caddy-vhosts/command-functions Executable file → Normal file
View File

@@ -30,113 +30,6 @@ cmd-caddy-labels-show() {
cmd-proxy-labels-show "proxy:labels:show" "caddy" "$@"
}
cmd-caddy-report() {
declare desc="displays a caddy report for one or more apps"
declare cmd="caddy:report"
[[ "$1" == "$cmd" ]] && shift 1
fn-report-parse-args "$@"
set -- "${REPORT_ARGS[@]}"
declare APP="${1:-}" INFO_FLAG="${2:-}"
if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then
INFO_FLAG="$APP"
APP=""
fi
if [[ "$REPORT_IS_GLOBAL" == "true" ]]; then
APP="--global"
fi
if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then
INFO_FLAG="true"
fi
if [[ "$APP" == "--global" ]]; then
cmd-caddy-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT"
elif [[ -z "$APP" ]]; then
for app in $(dokku_apps); do
cmd-caddy-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true
done
else
cmd-caddy-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT"
fi
}
cmd-caddy-report-single() {
declare APP="$1" INFO_FLAG="$2" FORMAT="${3:-stdout}"
if [[ "$INFO_FLAG" == "true" ]]; then
INFO_FLAG=""
fi
local flag_map=()
if [[ "$APP" == "--global" ]]; then
flag_map=(
"--caddy-computed-image: $(fn-caddy-computed-image)"
"--caddy-computed-letsencrypt-email: $(fn-caddy-computed-letsencrypt-email)"
"--caddy-computed-letsencrypt-server: $(fn-caddy-computed-letsencrypt-server)"
"--caddy-computed-log-level: $(fn-caddy-computed-log-level)"
"--caddy-computed-polling-interval: $(fn-caddy-computed-polling-interval)"
"--caddy-computed-tls-internal: $(fn-caddy-computed-tls-internal "$APP")"
"--caddy-global-image: $(fn-caddy-global-image)"
"--caddy-global-letsencrypt-email: $(fn-caddy-global-letsencrypt-email)"
"--caddy-global-letsencrypt-server: $(fn-caddy-global-letsencrypt-server)"
"--caddy-global-log-level: $(fn-caddy-global-log-level)"
"--caddy-global-polling-interval: $(fn-caddy-global-polling-interval)"
"--caddy-global-tls-internal: $(fn-caddy-global-tls-internal)"
)
else
verify_app_name "$APP"
flag_map=(
"--caddy-computed-image: $(fn-caddy-computed-image)"
"--caddy-computed-letsencrypt-email: $(fn-caddy-computed-letsencrypt-email)"
"--caddy-computed-letsencrypt-server: $(fn-caddy-computed-letsencrypt-server)"
"--caddy-computed-log-level: $(fn-caddy-computed-log-level)"
"--caddy-computed-polling-interval: $(fn-caddy-computed-polling-interval)"
"--caddy-computed-tls-internal: $(fn-caddy-computed-tls-internal "$APP")"
"--caddy-global-image: $(fn-caddy-global-image)"
"--caddy-global-letsencrypt-email: $(fn-caddy-global-letsencrypt-email)"
"--caddy-global-letsencrypt-server: $(fn-caddy-global-letsencrypt-server)"
"--caddy-global-log-level: $(fn-caddy-global-log-level)"
"--caddy-global-polling-interval: $(fn-caddy-global-polling-interval)"
"--caddy-global-tls-internal: $(fn-caddy-global-tls-internal)"
"--caddy-tls-internal: $(fn-caddy-tls-internal "$APP")"
)
fi
fn-report-validate-format "$FORMAT" "$INFO_FLAG"
if [[ "$FORMAT" == "json" ]]; then
fn-report-emit-json flag_map "caddy"
return
fi
if [[ -z "$INFO_FLAG" ]]; then
if [[ "$APP" == "--global" ]]; then
dokku_log_info2_quiet "global caddy information"
else
dokku_log_info2_quiet "${APP} caddy information"
fi
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
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
else
match=true
fi
fi
done
[[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}"
fi
}
cmd-caddy-logs() {
declare desc="display caddy logs from command line"
declare cmd="caddy:logs"

View File

@@ -0,0 +1,29 @@
module github.com/dokku/dokku/plugins/caddy-vhosts
go 1.26.2
require (
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
github.com/spf13/pflag v1.0.10
)
require (
github.com/alexellis/go-execute/v2 v2.2.1 // indirect
github.com/fatih/color v1.19.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.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/melbahja/goph v1.5.1 // 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.10 // indirect
github.com/ryanuber/columnize v2.1.2+incompatible // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/sys v0.46.0 // indirect
)
replace github.com/dokku/dokku/plugins/common => ../common

View File

@@ -0,0 +1,55 @@
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
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/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.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
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.5.1 h1:gdZyjn5nM6FO6mAZrv7XqE2gArQbb+V6FdIyH3Ru3vA=
github.com/melbahja/goph v1.5.1/go.mod h1:dDwo+44cmvfDLdiVpc6fJxexf5BA5yEDUeE5YgtuDO4=
github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I=
github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
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.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
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/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/caddy-vhosts/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-caddy-report-single "$@"

View File

@@ -0,0 +1,155 @@
package caddyvhosts
import (
"os"
"path/filepath"
"strings"
"github.com/dokku/dokku/plugins/common"
)
// ReportSingleApp is an internal function that displays the caddy report for one or more apps
func ReportSingleApp(appName string, format string, infoFlag string) error {
if appName != "--global" {
if err := common.VerifyAppName(appName); err != nil {
return err
}
}
flags := map[string]common.ReportFunc{
"--caddy-computed-image": reportComputedImage,
"--caddy-computed-letsencrypt-email": reportComputedLetsencryptEmail,
"--caddy-computed-letsencrypt-server": reportComputedLetsencryptServer,
"--caddy-computed-log-level": reportComputedLogLevel,
"--caddy-computed-polling-interval": reportComputedPollingInterval,
"--caddy-computed-tls-internal": reportComputedTLSInternal,
"--caddy-global-image": reportGlobalImage,
"--caddy-global-letsencrypt-email": reportGlobalLetsencryptEmail,
"--caddy-global-letsencrypt-server": reportGlobalLetsencryptServer,
"--caddy-global-log-level": reportGlobalLogLevel,
"--caddy-global-polling-interval": reportGlobalPollingInterval,
"--caddy-global-tls-internal": reportGlobalTLSInternal,
}
if appName != "--global" {
flags["--caddy-tls-internal"] = reportTLSInternal
}
flagKeys := []string{}
for flagKey := range flags {
flagKeys = append(flagKeys, flagKey)
}
infoFlags := common.CollectReport(appName, infoFlag, flags)
return common.ReportSingleApp(common.ReportSingleAppInput{
ReportType: "caddy",
AppName: appName,
InfoFlag: infoFlag,
InfoFlags: infoFlags,
InfoFlagKeys: flagKeys,
Format: format,
TrimPrefix: true,
UppercaseFirstCharacter: true,
EmitLegacyPrefix: false,
})
}
func reportGlobalImage(appName string) string {
return common.PropertyGet("caddy", "--global", "image")
}
func reportComputedImage(appName string) string {
value := reportGlobalImage(appName)
if value == "" {
value = dockerfileFromImage("caddy-vhosts")
}
return value
}
func reportGlobalLetsencryptEmail(appName string) string {
return common.PropertyGet("caddy", "--global", "letsencrypt-email")
}
func reportComputedLetsencryptEmail(appName string) string {
return reportGlobalLetsencryptEmail(appName)
}
func reportGlobalLetsencryptServer(appName string) string {
return common.PropertyGet("caddy", "--global", "letsencrypt-server")
}
func reportComputedLetsencryptServer(appName string) string {
value := reportGlobalLetsencryptServer(appName)
if value == "" {
value = "https://acme-v02.api.letsencrypt.org/directory"
}
return value
}
func reportGlobalLogLevel(appName string) string {
return common.PropertyGet("caddy", "--global", "log-level")
}
func reportComputedLogLevel(appName string) string {
value := reportGlobalLogLevel(appName)
if value == "" {
value = "ERROR"
}
return strings.ToUpper(value)
}
func reportGlobalPollingInterval(appName string) string {
return common.PropertyGet("caddy", "--global", "polling-interval")
}
func reportComputedPollingInterval(appName string) string {
value := reportGlobalPollingInterval(appName)
if value == "" {
value = "5s"
}
return value
}
func reportTLSInternal(appName string) string {
return common.PropertyGet("caddy", appName, "tls-internal")
}
func reportGlobalTLSInternal(appName string) string {
return common.PropertyGet("caddy", "--global", "tls-internal")
}
func reportComputedTLSInternal(appName string) string {
value := reportTLSInternal(appName)
if value == "" {
value = reportGlobalTLSInternal(appName)
}
if value == "" {
value = "false"
}
return value
}
// dockerfileFromImage returns the image referenced by the FROM line of the named
// plugin's Dockerfile, or an empty string when it cannot be read
func dockerfileFromImage(pluginName string) string {
pluginPath := os.Getenv("PLUGIN_AVAILABLE_PATH")
data, err := os.ReadFile(filepath.Join(pluginPath, pluginName, "Dockerfile"))
if err != nil {
return ""
}
for _, line := range strings.Split(string(data), "\n") {
if strings.Contains(line, "FROM") {
fields := strings.Fields(line)
if len(fields) >= 2 {
return fields[1]
}
}
}
return ""
}

View File

@@ -0,0 +1,40 @@
package main
import (
"fmt"
"os"
"strings"
caddyvhosts "github.com/dokku/dokku/plugins/caddy-vhosts"
"github.com/dokku/dokku/plugins/common"
flag "github.com/spf13/pflag"
)
// main entrypoint to all subcommands
func main() {
parts := strings.Split(os.Args[0], "/")
subcommand := parts[len(parts)-1]
var err error
switch subcommand {
case "report":
args := flag.NewFlagSet("caddy-vhosts:report", flag.ExitOnError)
format := args.String("format", "stdout", "format: [ stdout | json ]")
reportArgs, flagErr := common.ParseReportArgs("caddy-vhosts", os.Args[2:])
if flagErr == nil {
args.Parse(reportArgs.OSArgs)
appName := args.Arg(0)
if reportArgs.IsGlobal {
appName = "--global"
}
err = caddyvhosts.CommandReport(appName, *format, reportArgs.InfoFlag)
}
default:
err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand)
}
if err != nil {
common.LogFailWithError(err)
}
}

View File

@@ -0,0 +1,31 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
caddyvhosts "github.com/dokku/dokku/plugins/caddy-vhosts"
"github.com/dokku/dokku/plugins/common"
)
// main entrypoint to all triggers
func main() {
parts := strings.Split(os.Args[0], "/")
trigger := parts[len(parts)-1]
flag.Parse()
var err error
switch trigger {
case "report":
appName := flag.Arg(0)
err = caddyvhosts.ReportSingleApp(appName, "", "")
default:
err = fmt.Errorf("Invalid plugin trigger call: %s", trigger)
}
if err != nil {
common.LogFailWithError(err)
}
}

View File

@@ -0,0 +1,29 @@
package caddyvhosts
import (
"errors"
"github.com/dokku/dokku/plugins/common"
)
// CommandReport displays a caddy-vhosts report for one or more apps
func CommandReport(appName string, format string, infoFlag string) error {
if len(appName) == 0 {
apps, err := common.DokkuApps()
if err != nil {
if errors.Is(err, common.NoAppsExist) {
common.LogWarn(err.Error())
return nil
}
return err
}
for _, appName := range apps {
if err := ReportSingleApp(appName, format, infoFlag); err != nil {
return err
}
}
return nil
}
return ReportSingleApp(appName, format, infoFlag)
}

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/caddy-vhosts/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-caddy-report "$@"

4
plugins/haproxy-vhosts/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/report
/report-subcommand
/triggers
/subcommands/report

View File

@@ -0,0 +1,13 @@
GOARCH ?= amd64
TRIGGERS = triggers/report
BUILD = report-subcommand triggers
PLUGIN_NAME = haproxy-vhosts
clean-report-subcommand:
rm -rf report-subcommand
report-subcommand: clean-report-subcommand src/subcommands/subcommands.go
GOARCH=$(GOARCH) go build -mod=readonly -ldflags="-s -w" $(GO_ARGS) -o report-subcommand src/subcommands/subcommands.go
ln -sf ../report-subcommand subcommands/report
include ../../common.mk

88
plugins/haproxy-vhosts/command-functions Executable file → Normal file
View File

@@ -30,94 +30,6 @@ cmd-haproxy-labels-show() {
cmd-proxy-labels-show "proxy:labels:show" "haproxy" "$@"
}
cmd-haproxy-report() {
declare desc="displays a haproxy report for one or more apps"
declare cmd="haproxy:report"
[[ "$1" == "$cmd" ]] && shift 1
fn-report-parse-args "$@"
set -- "${REPORT_ARGS[@]}"
declare APP="${1:-}" INFO_FLAG="${2:-}"
if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then
INFO_FLAG="$APP"
APP=""
fi
if [[ "$REPORT_IS_GLOBAL" == "true" ]]; then
APP="--global"
fi
if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then
INFO_FLAG="true"
fi
if [[ "$APP" == "--global" ]]; then
cmd-haproxy-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT"
elif [[ -z "$APP" ]]; then
for app in $(dokku_apps); do
cmd-haproxy-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true
done
else
cmd-haproxy-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT"
fi
}
cmd-haproxy-report-single() {
declare APP="$1" INFO_FLAG="$2" FORMAT="${3:-stdout}"
if [[ "$INFO_FLAG" == "true" ]]; then
INFO_FLAG=""
fi
if [[ "$APP" != "--global" ]]; then
verify_app_name "$APP"
fi
local flag_map=(
"--haproxy-computed-image: $(fn-haproxy-computed-image)"
"--haproxy-computed-letsencrypt-email: $(fn-haproxy-computed-letsencrypt-email)"
"--haproxy-computed-letsencrypt-server: $(fn-haproxy-computed-letsencrypt-server)"
"--haproxy-computed-log-level: $(fn-haproxy-computed-log-level)"
"--haproxy-computed-refresh-conf: $(fn-haproxy-computed-refresh-conf)"
"--haproxy-global-image: $(fn-haproxy-global-image)"
"--haproxy-global-letsencrypt-email: $(fn-haproxy-global-letsencrypt-email)"
"--haproxy-global-letsencrypt-server: $(fn-haproxy-global-letsencrypt-server)"
"--haproxy-global-log-level: $(fn-haproxy-global-log-level)"
"--haproxy-global-refresh-conf: $(fn-haproxy-global-refresh-conf)"
)
fn-report-validate-format "$FORMAT" "$INFO_FLAG"
if [[ "$FORMAT" == "json" ]]; then
fn-report-emit-json flag_map "haproxy"
return
fi
if [[ -z "$INFO_FLAG" ]]; then
if [[ "$APP" == "--global" ]]; then
dokku_log_info2_quiet "global haproxy information"
else
dokku_log_info2_quiet "${APP} haproxy information"
fi
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
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
else
match=true
fi
fi
done
[[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}"
fi
}
cmd-haproxy-logs() {
declare desc="display haproxy logs from command line"
declare cmd="haproxy:logs"

View File

@@ -0,0 +1,29 @@
module github.com/dokku/dokku/plugins/haproxy-vhosts
go 1.26.2
require (
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
github.com/spf13/pflag v1.0.10
)
require (
github.com/alexellis/go-execute/v2 v2.2.1 // indirect
github.com/fatih/color v1.19.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.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/melbahja/goph v1.5.1 // 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.10 // indirect
github.com/ryanuber/columnize v2.1.2+incompatible // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/sys v0.46.0 // indirect
)
replace github.com/dokku/dokku/plugins/common => ../common

View File

@@ -0,0 +1,55 @@
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
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/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.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
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.5.1 h1:gdZyjn5nM6FO6mAZrv7XqE2gArQbb+V6FdIyH3Ru3vA=
github.com/melbahja/goph v1.5.1/go.mod h1:dDwo+44cmvfDLdiVpc6fJxexf5BA5yEDUeE5YgtuDO4=
github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I=
github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
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.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
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/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/haproxy-vhosts/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-haproxy-report-single "$@"

View File

@@ -0,0 +1,130 @@
package haproxyvhosts
import (
"os"
"path/filepath"
"strings"
"github.com/dokku/dokku/plugins/common"
)
// ReportSingleApp is an internal function that displays the haproxy report for one or more apps
func ReportSingleApp(appName string, format string, infoFlag string) error {
if appName != "--global" {
if err := common.VerifyAppName(appName); err != nil {
return err
}
}
flags := map[string]common.ReportFunc{
"--haproxy-computed-image": reportComputedImage,
"--haproxy-computed-letsencrypt-email": reportComputedLetsencryptEmail,
"--haproxy-computed-letsencrypt-server": reportComputedLetsencryptServer,
"--haproxy-computed-log-level": reportComputedLogLevel,
"--haproxy-computed-refresh-conf": reportComputedRefreshConf,
"--haproxy-global-image": reportGlobalImage,
"--haproxy-global-letsencrypt-email": reportGlobalLetsencryptEmail,
"--haproxy-global-letsencrypt-server": reportGlobalLetsencryptServer,
"--haproxy-global-log-level": reportGlobalLogLevel,
"--haproxy-global-refresh-conf": reportGlobalRefreshConf,
}
flagKeys := []string{}
for flagKey := range flags {
flagKeys = append(flagKeys, flagKey)
}
infoFlags := common.CollectReport(appName, infoFlag, flags)
return common.ReportSingleApp(common.ReportSingleAppInput{
ReportType: "haproxy",
AppName: appName,
InfoFlag: infoFlag,
InfoFlags: infoFlags,
InfoFlagKeys: flagKeys,
Format: format,
TrimPrefix: true,
UppercaseFirstCharacter: true,
EmitLegacyPrefix: false,
})
}
func reportGlobalImage(appName string) string {
return common.PropertyGet("haproxy", "--global", "image")
}
func reportComputedImage(appName string) string {
value := reportGlobalImage(appName)
if value == "" {
value = dockerfileFromImage("haproxy-vhosts")
}
return value
}
func reportGlobalLetsencryptEmail(appName string) string {
return common.PropertyGet("haproxy", "--global", "letsencrypt-email")
}
func reportComputedLetsencryptEmail(appName string) string {
return reportGlobalLetsencryptEmail(appName)
}
func reportGlobalLetsencryptServer(appName string) string {
return common.PropertyGet("haproxy", "--global", "letsencrypt-server")
}
func reportComputedLetsencryptServer(appName string) string {
value := reportGlobalLetsencryptServer(appName)
if value == "" {
value = "https://acme-v02.api.letsencrypt.org/directory"
}
return value
}
func reportGlobalLogLevel(appName string) string {
return common.PropertyGet("haproxy", "--global", "log-level")
}
func reportComputedLogLevel(appName string) string {
value := reportGlobalLogLevel(appName)
if value == "" {
value = "ERROR"
}
return strings.ToUpper(value)
}
func reportGlobalRefreshConf(appName string) string {
return common.PropertyGet("haproxy", "--global", "refresh-conf")
}
func reportComputedRefreshConf(appName string) string {
value := reportGlobalRefreshConf(appName)
if value == "" {
value = "10"
}
return value
}
// dockerfileFromImage returns the image referenced by the FROM line of the named
// plugin's Dockerfile, or an empty string when it cannot be read
func dockerfileFromImage(pluginName string) string {
pluginPath := os.Getenv("PLUGIN_AVAILABLE_PATH")
data, err := os.ReadFile(filepath.Join(pluginPath, pluginName, "Dockerfile"))
if err != nil {
return ""
}
for _, line := range strings.Split(string(data), "\n") {
if strings.Contains(line, "FROM") {
fields := strings.Fields(line)
if len(fields) >= 2 {
return fields[1]
}
}
}
return ""
}

View File

@@ -0,0 +1,40 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
haproxyvhosts "github.com/dokku/dokku/plugins/haproxy-vhosts"
flag "github.com/spf13/pflag"
)
// main entrypoint to all subcommands
func main() {
parts := strings.Split(os.Args[0], "/")
subcommand := parts[len(parts)-1]
var err error
switch subcommand {
case "report":
args := flag.NewFlagSet("haproxy-vhosts:report", flag.ExitOnError)
format := args.String("format", "stdout", "format: [ stdout | json ]")
reportArgs, flagErr := common.ParseReportArgs("haproxy-vhosts", os.Args[2:])
if flagErr == nil {
args.Parse(reportArgs.OSArgs)
appName := args.Arg(0)
if reportArgs.IsGlobal {
appName = "--global"
}
err = haproxyvhosts.CommandReport(appName, *format, reportArgs.InfoFlag)
}
default:
err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand)
}
if err != nil {
common.LogFailWithError(err)
}
}

View File

@@ -0,0 +1,31 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
haproxyvhosts "github.com/dokku/dokku/plugins/haproxy-vhosts"
)
// main entrypoint to all triggers
func main() {
parts := strings.Split(os.Args[0], "/")
trigger := parts[len(parts)-1]
flag.Parse()
var err error
switch trigger {
case "report":
appName := flag.Arg(0)
err = haproxyvhosts.ReportSingleApp(appName, "", "")
default:
err = fmt.Errorf("Invalid plugin trigger call: %s", trigger)
}
if err != nil {
common.LogFailWithError(err)
}
}

View File

@@ -0,0 +1,29 @@
package haproxyvhosts
import (
"errors"
"github.com/dokku/dokku/plugins/common"
)
// CommandReport displays a haproxy-vhosts report for one or more apps
func CommandReport(appName string, format string, infoFlag string) error {
if len(appName) == 0 {
apps, err := common.DokkuApps()
if err != nil {
if errors.Is(err, common.NoAppsExist) {
common.LogWarn(err.Error())
return nil
}
return err
}
for _, appName := range apps {
if err := ReportSingleApp(appName, format, infoFlag); err != nil {
return err
}
}
return nil
}
return ReportSingleApp(appName, format, infoFlag)
}

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/haproxy-vhosts/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-haproxy-report "$@"

4
plugins/openresty-vhosts/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/report
/report-subcommand
/triggers
/subcommands/report

View File

@@ -0,0 +1,13 @@
GOARCH ?= amd64
TRIGGERS = triggers/report
BUILD = report-subcommand triggers
PLUGIN_NAME = openresty-vhosts
clean-report-subcommand:
rm -rf report-subcommand
report-subcommand: clean-report-subcommand src/subcommands/subcommands.go
GOARCH=$(GOARCH) go build -mod=readonly -ldflags="-s -w" $(GO_ARGS) -o report-subcommand src/subcommands/subcommands.go
ln -sf ../report-subcommand subcommands/report
include ../../common.mk

237
plugins/openresty-vhosts/command-functions Executable file → Normal file
View File

@@ -30,243 +30,6 @@ cmd-openresty-labels-show() {
cmd-proxy-labels-show "proxy:labels:show" "openresty" "$@"
}
cmd-openresty-report() {
declare desc="displays a openresty report for one or more apps"
declare cmd="openresty:report"
[[ "$1" == "$cmd" ]] && shift 1
fn-report-parse-args "$@"
set -- "${REPORT_ARGS[@]}"
declare APP="${1:-}" INFO_FLAG="${2:-}"
if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then
INFO_FLAG="$APP"
APP=""
fi
if [[ "$REPORT_IS_GLOBAL" == "true" ]]; then
APP="--global"
fi
if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then
INFO_FLAG="true"
fi
if [[ "$APP" == "--global" ]]; then
cmd-openresty-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT"
elif [[ -z "$APP" ]]; then
for app in $(dokku_apps); do
cmd-openresty-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true
done
else
cmd-openresty-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT"
fi
}
cmd-openresty-report-single() {
declare APP="$1" INFO_FLAG="$2" FORMAT="${3:-stdout}"
if [[ "$INFO_FLAG" == "true" ]]; then
INFO_FLAG=""
fi
local flag_map=()
if [[ "$APP" == "--global" ]]; then
flag_map=(
"--openresty-global-access-log-format: $(fn-openresty-global-access-log-format)"
"--openresty-computed-access-log-format: $(fn-openresty-computed-access-log-format "$APP")"
"--openresty-global-access-log-path: $(fn-openresty-global-access-log-path)"
"--openresty-computed-access-log-path: $(fn-openresty-computed-access-log-path "$APP")"
"--openresty-global-allowed-letsencrypt-domains-func-base64: $(fn-openresty-global-allowed-letsencrypt-domains-func-base64)"
"--openresty-computed-allowed-letsencrypt-domains-func-base64: $(fn-openresty-computed-allowed-letsencrypt-domains-func-base64)"
"--openresty-global-bind-address-ipv4: $(fn-openresty-global-bind-address-ipv4)"
"--openresty-computed-bind-address-ipv4: $(fn-openresty-computed-bind-address-ipv4 "$APP")"
"--openresty-global-bind-address-ipv6: $(fn-openresty-global-bind-address-ipv6)"
"--openresty-computed-bind-address-ipv6: $(fn-openresty-computed-bind-address-ipv6 "$APP")"
"--openresty-global-client-body-timeout: $(fn-openresty-global-client-body-timeout)"
"--openresty-computed-client-body-timeout: $(fn-openresty-computed-client-body-timeout "$APP")"
"--openresty-global-client-header-timeout: $(fn-openresty-global-client-header-timeout)"
"--openresty-computed-client-header-timeout: $(fn-openresty-computed-client-header-timeout "$APP")"
"--openresty-global-client-max-body-size: $(fn-openresty-global-client-max-body-size)"
"--openresty-computed-client-max-body-size: $(fn-openresty-computed-client-max-body-size "$APP")"
"--openresty-global-error-log-path: $(fn-openresty-global-error-log-path)"
"--openresty-computed-error-log-path: $(fn-openresty-computed-error-log-path "$APP")"
"--openresty-global-hsts: $(fn-plugin-property-get-default "openresty" "--global" "hsts" "")"
"--openresty-computed-hsts: $(fn-openresty-hsts-is-enabled "--global")"
"--openresty-global-hsts-include-subdomains: $(fn-openresty-global-hsts-include-subdomains)"
"--openresty-computed-hsts-include-subdomains: $(fn-openresty-computed-hsts-include-subdomains "$APP")"
"--openresty-global-hsts-max-age: $(fn-openresty-global-hsts-max-age)"
"--openresty-computed-hsts-max-age: $(fn-openresty-computed-hsts-max-age "$APP")"
"--openresty-global-hsts-preload: $(fn-openresty-global-hsts-preload)"
"--openresty-computed-hsts-preload: $(fn-openresty-computed-hsts-preload "$APP")"
"--openresty-global-image: $(fn-openresty-global-image)"
"--openresty-computed-image: $(fn-openresty-computed-image)"
"--openresty-global-keepalive-timeout: $(fn-openresty-global-keepalive-timeout)"
"--openresty-computed-keepalive-timeout: $(fn-openresty-computed-keepalive-timeout "$APP")"
"--openresty-global-letsencrypt-email: $(fn-openresty-global-letsencrypt-email)"
"--openresty-computed-letsencrypt-email: $(fn-openresty-computed-letsencrypt-email)"
"--openresty-global-letsencrypt-server: $(fn-openresty-global-letsencrypt-server)"
"--openresty-computed-letsencrypt-server: $(fn-openresty-computed-letsencrypt-server)"
"--openresty-global-lingering-timeout: $(fn-openresty-global-lingering-timeout)"
"--openresty-computed-lingering-timeout: $(fn-openresty-computed-lingering-timeout "$APP")"
"--openresty-global-log-level: $(fn-openresty-global-log-level)"
"--openresty-computed-log-level: $(fn-openresty-computed-log-level)"
"--openresty-global-proxy-buffer-size: $(fn-openresty-global-proxy-buffer-size)"
"--openresty-computed-proxy-buffer-size: $(fn-openresty-computed-proxy-buffer-size "$APP")"
"--openresty-global-proxy-buffering: $(fn-openresty-global-proxy-buffering)"
"--openresty-computed-proxy-buffering: $(fn-openresty-computed-proxy-buffering "$APP")"
"--openresty-global-proxy-buffers: $(fn-openresty-global-proxy-buffers)"
"--openresty-computed-proxy-buffers: $(fn-openresty-computed-proxy-buffers "$APP")"
"--openresty-global-proxy-busy-buffers-size: $(fn-openresty-global-proxy-busy-buffers-size)"
"--openresty-computed-proxy-busy-buffers-size: $(fn-openresty-computed-proxy-busy-buffers-size "$APP")"
"--openresty-global-proxy-connect-timeout: $(fn-openresty-global-proxy-connect-timeout)"
"--openresty-computed-proxy-connect-timeout: $(fn-openresty-computed-proxy-connect-timeout "$APP")"
"--openresty-global-proxy-read-timeout: $(fn-openresty-global-proxy-read-timeout)"
"--openresty-computed-proxy-read-timeout: $(fn-openresty-computed-proxy-read-timeout "$APP")"
"--openresty-global-proxy-send-timeout: $(fn-openresty-global-proxy-send-timeout)"
"--openresty-computed-proxy-send-timeout: $(fn-openresty-computed-proxy-send-timeout "$APP")"
"--openresty-global-send-timeout: $(fn-openresty-global-send-timeout)"
"--openresty-computed-send-timeout: $(fn-openresty-computed-send-timeout "$APP")"
"--openresty-global-underscore-in-headers: $(fn-openresty-global-underscore-in-headers)"
"--openresty-computed-underscore-in-headers: $(fn-openresty-computed-underscore-in-headers "$APP")"
"--openresty-global-x-forwarded-for-value: $(fn-openresty-global-x-forwarded-for-value)"
"--openresty-computed-x-forwarded-for-value: $(fn-openresty-computed-x-forwarded-for-value "$APP")"
"--openresty-global-x-forwarded-port-value: $(fn-openresty-global-x-forwarded-port-value)"
"--openresty-computed-x-forwarded-port-value: $(fn-openresty-computed-x-forwarded-port-value "$APP")"
"--openresty-global-x-forwarded-proto-value: $(fn-openresty-global-x-forwarded-proto-value)"
"--openresty-computed-x-forwarded-proto-value: $(fn-openresty-computed-x-forwarded-proto-value "$APP")"
"--openresty-global-x-forwarded-ssl: $(fn-openresty-global-x-forwarded-ssl)"
"--openresty-computed-x-forwarded-ssl: $(fn-openresty-computed-x-forwarded-ssl "$APP")"
)
else
verify_app_name "$APP"
flag_map=(
"--openresty-access-log-format: $(fn-openresty-access-log-format "$APP")"
"--openresty-computed-access-log-format: $(fn-openresty-computed-access-log-format "$APP")"
"--openresty-global-access-log-format: $(fn-openresty-global-access-log-format)"
"--openresty-access-log-path: $(fn-openresty-access-log-path "$APP")"
"--openresty-computed-access-log-path: $(fn-openresty-computed-access-log-path "$APP")"
"--openresty-global-access-log-path: $(fn-openresty-global-access-log-path)"
"--openresty-global-allowed-letsencrypt-domains-func-base64: $(fn-openresty-global-allowed-letsencrypt-domains-func-base64)"
"--openresty-computed-allowed-letsencrypt-domains-func-base64: $(fn-openresty-computed-allowed-letsencrypt-domains-func-base64)"
"--openresty-bind-address-ipv4: $(fn-openresty-bind-address-ipv4 "$APP")"
"--openresty-computed-bind-address-ipv4: $(fn-openresty-computed-bind-address-ipv4 "$APP")"
"--openresty-global-bind-address-ipv4: $(fn-openresty-global-bind-address-ipv4)"
"--openresty-bind-address-ipv6: $(fn-openresty-bind-address-ipv6 "$APP")"
"--openresty-computed-bind-address-ipv6: $(fn-openresty-computed-bind-address-ipv6 "$APP")"
"--openresty-global-bind-address-ipv6: $(fn-openresty-global-bind-address-ipv6)"
"--openresty-client-body-timeout: $(fn-openresty-client-body-timeout "$APP")"
"--openresty-computed-client-body-timeout: $(fn-openresty-computed-client-body-timeout "$APP")"
"--openresty-global-client-body-timeout: $(fn-openresty-global-client-body-timeout)"
"--openresty-client-header-timeout: $(fn-openresty-client-header-timeout "$APP")"
"--openresty-computed-client-header-timeout: $(fn-openresty-computed-client-header-timeout "$APP")"
"--openresty-global-client-header-timeout: $(fn-openresty-global-client-header-timeout)"
"--openresty-client-max-body-size: $(fn-openresty-client-max-body-size "$APP")"
"--openresty-computed-client-max-body-size: $(fn-openresty-computed-client-max-body-size "$APP")"
"--openresty-global-client-max-body-size: $(fn-openresty-global-client-max-body-size)"
"--openresty-error-log-path: $(fn-openresty-error-log-path "$APP")"
"--openresty-computed-error-log-path: $(fn-openresty-computed-error-log-path "$APP")"
"--openresty-global-error-log-path: $(fn-openresty-global-error-log-path)"
"--openresty-hsts: $(fn-plugin-property-get-default "openresty" "$APP" "hsts" "")"
"--openresty-computed-hsts: $(fn-openresty-hsts-is-enabled "$APP")"
"--openresty-global-hsts: $(fn-plugin-property-get-default "openresty" "--global" "hsts" "")"
"--openresty-hsts-include-subdomains: $(fn-openresty-hsts-include-subdomains "$APP")"
"--openresty-computed-hsts-include-subdomains: $(fn-openresty-computed-hsts-include-subdomains "$APP")"
"--openresty-global-hsts-include-subdomains: $(fn-openresty-global-hsts-include-subdomains)"
"--openresty-hsts-max-age: $(fn-openresty-hsts-max-age "$APP")"
"--openresty-computed-hsts-max-age: $(fn-openresty-computed-hsts-max-age "$APP")"
"--openresty-global-hsts-max-age: $(fn-openresty-global-hsts-max-age)"
"--openresty-hsts-preload: $(fn-openresty-hsts-preload "$APP")"
"--openresty-computed-hsts-preload: $(fn-openresty-computed-hsts-preload "$APP")"
"--openresty-global-hsts-preload: $(fn-openresty-global-hsts-preload)"
"--openresty-global-image: $(fn-openresty-global-image)"
"--openresty-computed-image: $(fn-openresty-computed-image)"
"--openresty-keepalive-timeout: $(fn-openresty-keepalive-timeout "$APP")"
"--openresty-computed-keepalive-timeout: $(fn-openresty-computed-keepalive-timeout "$APP")"
"--openresty-global-keepalive-timeout: $(fn-openresty-global-keepalive-timeout)"
"--openresty-global-letsencrypt-email: $(fn-openresty-global-letsencrypt-email)"
"--openresty-computed-letsencrypt-email: $(fn-openresty-computed-letsencrypt-email)"
"--openresty-global-letsencrypt-server: $(fn-openresty-global-letsencrypt-server)"
"--openresty-computed-letsencrypt-server: $(fn-openresty-computed-letsencrypt-server)"
"--openresty-lingering-timeout: $(fn-openresty-lingering-timeout "$APP")"
"--openresty-computed-lingering-timeout: $(fn-openresty-computed-lingering-timeout "$APP")"
"--openresty-global-lingering-timeout: $(fn-openresty-global-lingering-timeout)"
"--openresty-global-log-level: $(fn-openresty-global-log-level)"
"--openresty-computed-log-level: $(fn-openresty-computed-log-level)"
"--openresty-proxy-buffer-size: $(fn-openresty-proxy-buffer-size "$APP")"
"--openresty-computed-proxy-buffer-size: $(fn-openresty-computed-proxy-buffer-size "$APP")"
"--openresty-global-proxy-buffer-size: $(fn-openresty-global-proxy-buffer-size)"
"--openresty-proxy-buffering: $(fn-openresty-proxy-buffering "$APP")"
"--openresty-computed-proxy-buffering: $(fn-openresty-computed-proxy-buffering "$APP")"
"--openresty-global-proxy-buffering: $(fn-openresty-global-proxy-buffering)"
"--openresty-proxy-buffers: $(fn-openresty-proxy-buffers "$APP")"
"--openresty-computed-proxy-buffers: $(fn-openresty-computed-proxy-buffers "$APP")"
"--openresty-global-proxy-buffers: $(fn-openresty-global-proxy-buffers)"
"--openresty-proxy-busy-buffers-size: $(fn-openresty-proxy-busy-buffers-size "$APP")"
"--openresty-computed-proxy-busy-buffers-size: $(fn-openresty-computed-proxy-busy-buffers-size "$APP")"
"--openresty-global-proxy-busy-buffers-size: $(fn-openresty-global-proxy-busy-buffers-size)"
"--openresty-proxy-connect-timeout: $(fn-openresty-proxy-connect-timeout "$APP")"
"--openresty-computed-proxy-connect-timeout: $(fn-openresty-computed-proxy-connect-timeout "$APP")"
"--openresty-global-proxy-connect-timeout: $(fn-openresty-global-proxy-connect-timeout)"
"--openresty-proxy-read-timeout: $(fn-openresty-proxy-read-timeout "$APP")"
"--openresty-computed-proxy-read-timeout: $(fn-openresty-computed-proxy-read-timeout "$APP")"
"--openresty-global-proxy-read-timeout: $(fn-openresty-global-proxy-read-timeout)"
"--openresty-proxy-send-timeout: $(fn-openresty-proxy-send-timeout "$APP")"
"--openresty-computed-proxy-send-timeout: $(fn-openresty-computed-proxy-send-timeout "$APP")"
"--openresty-global-proxy-send-timeout: $(fn-openresty-global-proxy-send-timeout)"
"--openresty-send-timeout: $(fn-openresty-send-timeout "$APP")"
"--openresty-computed-send-timeout: $(fn-openresty-computed-send-timeout "$APP")"
"--openresty-global-send-timeout: $(fn-openresty-global-send-timeout)"
"--openresty-underscore-in-headers: $(fn-openresty-underscore-in-headers "$APP")"
"--openresty-computed-underscore-in-headers: $(fn-openresty-computed-underscore-in-headers "$APP")"
"--openresty-global-underscore-in-headers: $(fn-openresty-global-underscore-in-headers)"
"--openresty-x-forwarded-for-value: $(fn-openresty-x-forwarded-for-value "$APP")"
"--openresty-computed-x-forwarded-for-value: $(fn-openresty-computed-x-forwarded-for-value "$APP")"
"--openresty-global-x-forwarded-for-value: $(fn-openresty-global-x-forwarded-for-value)"
"--openresty-x-forwarded-port-value: $(fn-openresty-x-forwarded-port-value "$APP")"
"--openresty-computed-x-forwarded-port-value: $(fn-openresty-computed-x-forwarded-port-value "$APP")"
"--openresty-global-x-forwarded-port-value: $(fn-openresty-global-x-forwarded-port-value)"
"--openresty-x-forwarded-proto-value: $(fn-openresty-x-forwarded-proto-value "$APP")"
"--openresty-computed-x-forwarded-proto-value: $(fn-openresty-computed-x-forwarded-proto-value "$APP")"
"--openresty-global-x-forwarded-proto-value: $(fn-openresty-global-x-forwarded-proto-value)"
"--openresty-x-forwarded-ssl: $(fn-openresty-x-forwarded-ssl "$APP")"
"--openresty-computed-x-forwarded-ssl: $(fn-openresty-computed-x-forwarded-ssl "$APP")"
"--openresty-global-x-forwarded-ssl: $(fn-openresty-global-x-forwarded-ssl)"
)
fi
fn-report-validate-format "$FORMAT" "$INFO_FLAG"
if [[ "$FORMAT" == "json" ]]; then
fn-report-emit-json flag_map "openresty"
return
fi
if [[ -z "$INFO_FLAG" ]]; then
if [[ "$APP" == "--global" ]]; then
dokku_log_info2_quiet "global openresty information"
else
dokku_log_info2_quiet "${APP} openresty information"
fi
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
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
else
match=true
fi
fi
done
[[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}"
fi
}
cmd-openresty-logs() {
declare desc="display openresty logs from command line"
declare cmd="openresty:logs"

View File

@@ -0,0 +1,29 @@
module github.com/dokku/dokku/plugins/openresty-vhosts
go 1.26.2
require (
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
github.com/spf13/pflag v1.0.10
)
require (
github.com/alexellis/go-execute/v2 v2.2.1 // indirect
github.com/fatih/color v1.19.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.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/melbahja/goph v1.5.1 // 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.10 // indirect
github.com/ryanuber/columnize v2.1.2+incompatible // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/sys v0.46.0 // indirect
)
replace github.com/dokku/dokku/plugins/common => ../common

View File

@@ -0,0 +1,55 @@
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
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/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.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
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.5.1 h1:gdZyjn5nM6FO6mAZrv7XqE2gArQbb+V6FdIyH3Ru3vA=
github.com/melbahja/goph v1.5.1/go.mod h1:dDwo+44cmvfDLdiVpc6fJxexf5BA5yEDUeE5YgtuDO4=
github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I=
github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
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.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
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/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-openresty-report-single "$@"

View File

@@ -0,0 +1,173 @@
package openrestyvhosts
import (
"encoding/base64"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/dokku/dokku/plugins/common"
)
const openrestyLogRoot = "/var/log/nginx"
// openrestyProp describes a settable openresty property along with the computed
// default used when neither an app-scoped nor a global value is present.
type openrestyProp struct {
name string
hasApp bool
computedDefault func(appName string) string
}
func staticDefault(value string) func(string) string {
return func(string) string { return value }
}
func openrestyPageSizeKB() int {
return os.Getpagesize() / 1024
}
func openrestyProps() []openrestyProp {
pageSize := openrestyPageSizeKB()
return []openrestyProp{
{"access-log-format", true, staticDefault("")},
{"access-log-path", true, func(appName string) string {
return fmt.Sprintf("%s/%s-access.log", openrestyLogRoot, appName)
}},
{"bind-address-ipv4", true, staticDefault("")},
{"bind-address-ipv6", true, staticDefault("::")},
{"client-body-timeout", true, staticDefault("60s")},
{"client-header-timeout", true, staticDefault("60s")},
{"client-max-body-size", true, staticDefault("1m")},
{"error-log-path", true, func(appName string) string {
return fmt.Sprintf("%s/%s-error.log", openrestyLogRoot, appName)
}},
{"hsts-include-subdomains", true, staticDefault("true")},
{"hsts-max-age", true, staticDefault("15724800")},
{"hsts-preload", true, staticDefault("false")},
{"image", false, func(string) string { return dockerfileFromImage("openresty-vhosts") }},
{"keepalive-timeout", true, staticDefault("75s")},
{"letsencrypt-email", false, staticDefault("")},
{"letsencrypt-server", false, staticDefault("https://acme-v02.api.letsencrypt.org/directory")},
{"lingering-timeout", true, staticDefault("5s")},
{"log-level", false, staticDefault("ERROR")},
{"proxy-buffer-size", true, staticDefault(fmt.Sprintf("%dk", pageSize))},
{"proxy-buffering", true, staticDefault("on")},
{"proxy-buffers", true, staticDefault(fmt.Sprintf("8 %dk", pageSize))},
{"proxy-busy-buffers-size", true, staticDefault(fmt.Sprintf("%dk", pageSize*2))},
{"proxy-connect-timeout", true, staticDefault("60s")},
{"proxy-read-timeout", true, staticDefault("60s")},
{"proxy-send-timeout", true, staticDefault("60s")},
{"send-timeout", true, staticDefault("60s")},
{"underscore-in-headers", true, staticDefault("off")},
{"x-forwarded-for-value", true, staticDefault("$remote_addr")},
{"x-forwarded-port-value", true, staticDefault("$server_port")},
{"x-forwarded-proto-value", true, staticDefault("$scheme")},
{"x-forwarded-ssl", true, staticDefault("")},
}
}
// ReportSingleApp is an internal function that displays the openresty report for one or more apps
func ReportSingleApp(appName string, format string, infoFlag string) error {
if appName != "--global" {
if err := common.VerifyAppName(appName); err != nil {
return err
}
}
isGlobal := appName == "--global"
flags := map[string]common.ReportFunc{}
for _, p := range openrestyProps() {
prop := p.name
hasApp := p.hasApp
computedDefault := p.computedDefault
if hasApp && !isGlobal {
flags["--openresty-"+prop] = func(app string) string {
return common.PropertyGet("openresty", app, prop)
}
}
flags["--openresty-global-"+prop] = func(app string) string {
return common.PropertyGet("openresty", "--global", prop)
}
flags["--openresty-computed-"+prop] = func(app string) string {
value := ""
if hasApp && app != "--global" {
value = common.PropertyGet("openresty", app, prop)
}
if value == "" {
value = common.PropertyGet("openresty", "--global", prop)
}
if value == "" {
value = computedDefault(app)
}
return value
}
}
flags["--openresty-global-allowed-letsencrypt-domains-func-base64"] = func(app string) string {
return common.PropertyGet("openresty", "--global", "allowed-letsencrypt-domains-func-base64")
}
flags["--openresty-computed-allowed-letsencrypt-domains-func-base64"] = func(app string) string {
value := common.PropertyGetDefault("openresty", "--global", "allowed-letsencrypt-domains-func-base64", "return true")
return base64.StdEncoding.EncodeToString([]byte(value))
}
if !isGlobal {
flags["--openresty-hsts"] = func(app string) string {
return common.PropertyGet("openresty", app, "hsts")
}
}
flags["--openresty-global-hsts"] = func(app string) string {
return common.PropertyGet("openresty", "--global", "hsts")
}
flags["--openresty-computed-hsts"] = openrestyHstsIsEnabled
flagKeys := []string{}
for flagKey := range flags {
flagKeys = append(flagKeys, flagKey)
}
infoFlags := common.CollectReport(appName, infoFlag, flags)
return common.ReportSingleApp(common.ReportSingleAppInput{
ReportType: "openresty",
AppName: appName,
InfoFlag: infoFlag,
InfoFlags: infoFlags,
InfoFlagKeys: flagKeys,
Format: format,
TrimPrefix: true,
UppercaseFirstCharacter: true,
EmitLegacyPrefix: false,
})
}
func openrestyHstsIsEnabled(appName string) string {
value := common.PropertyGet("openresty", appName, "hsts")
if value == "" {
value = common.PropertyGetDefault("openresty", "--global", "hsts", "true")
}
return value
}
// dockerfileFromImage returns the image referenced by the FROM line of the named
// plugin's Dockerfile, or an empty string when it cannot be read
func dockerfileFromImage(pluginName string) string {
pluginPath := os.Getenv("PLUGIN_AVAILABLE_PATH")
data, err := os.ReadFile(filepath.Join(pluginPath, pluginName, "Dockerfile"))
if err != nil {
return ""
}
for _, line := range strings.Split(string(data), "\n") {
if strings.Contains(line, "FROM") {
fields := strings.Fields(line)
if len(fields) >= 2 {
return fields[1]
}
}
}
return ""
}

View File

@@ -0,0 +1,40 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
openrestyvhosts "github.com/dokku/dokku/plugins/openresty-vhosts"
flag "github.com/spf13/pflag"
)
// main entrypoint to all subcommands
func main() {
parts := strings.Split(os.Args[0], "/")
subcommand := parts[len(parts)-1]
var err error
switch subcommand {
case "report":
args := flag.NewFlagSet("openresty-vhosts:report", flag.ExitOnError)
format := args.String("format", "stdout", "format: [ stdout | json ]")
reportArgs, flagErr := common.ParseReportArgs("openresty-vhosts", os.Args[2:])
if flagErr == nil {
args.Parse(reportArgs.OSArgs)
appName := args.Arg(0)
if reportArgs.IsGlobal {
appName = "--global"
}
err = openrestyvhosts.CommandReport(appName, *format, reportArgs.InfoFlag)
}
default:
err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand)
}
if err != nil {
common.LogFailWithError(err)
}
}

View File

@@ -0,0 +1,31 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
openrestyvhosts "github.com/dokku/dokku/plugins/openresty-vhosts"
)
// main entrypoint to all triggers
func main() {
parts := strings.Split(os.Args[0], "/")
trigger := parts[len(parts)-1]
flag.Parse()
var err error
switch trigger {
case "report":
appName := flag.Arg(0)
err = openrestyvhosts.ReportSingleApp(appName, "", "")
default:
err = fmt.Errorf("Invalid plugin trigger call: %s", trigger)
}
if err != nil {
common.LogFailWithError(err)
}
}

View File

@@ -0,0 +1,29 @@
package openrestyvhosts
import (
"errors"
"github.com/dokku/dokku/plugins/common"
)
// CommandReport displays a openresty-vhosts report for one or more apps
func CommandReport(appName string, format string, infoFlag string) error {
if len(appName) == 0 {
apps, err := common.DokkuApps()
if err != nil {
if errors.Is(err, common.NoAppsExist) {
common.LogWarn(err.Error())
return nil
}
return err
}
for _, appName := range apps {
if err := ReportSingleApp(appName, format, infoFlag); err != nil {
return err
}
}
return nil
}
return ReportSingleApp(appName, format, infoFlag)
}

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-openresty-report "$@"

4
plugins/traefik-vhosts/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/report
/report-subcommand
/triggers
/subcommands/report

View File

@@ -0,0 +1,13 @@
GOARCH ?= amd64
TRIGGERS = triggers/report
BUILD = report-subcommand triggers
PLUGIN_NAME = traefik-vhosts
clean-report-subcommand:
rm -rf report-subcommand
report-subcommand: clean-report-subcommand src/subcommands/subcommands.go
GOARCH=$(GOARCH) go build -mod=readonly -ldflags="-s -w" $(GO_ARGS) -o report-subcommand src/subcommands/subcommands.go
ln -sf ../report-subcommand subcommands/report
include ../../common.mk

125
plugins/traefik-vhosts/command-functions Executable file → Normal file
View File

@@ -30,131 +30,6 @@ cmd-traefik-labels-show() {
cmd-proxy-labels-show "proxy:labels:show" "traefik" "$@"
}
cmd-traefik-report() {
declare desc="displays a traefik report for one or more apps"
declare cmd="traefik:report"
[[ "$1" == "$cmd" ]] && shift 1
fn-report-parse-args "$@"
set -- "${REPORT_ARGS[@]}"
declare APP="${1:-}" INFO_FLAG="${2:-}"
if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then
INFO_FLAG="$APP"
APP=""
fi
if [[ "$REPORT_IS_GLOBAL" == "true" ]]; then
APP="--global"
fi
if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then
INFO_FLAG="true"
fi
if [[ "$APP" == "--global" ]]; then
cmd-traefik-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT"
elif [[ -z "$APP" ]]; then
for app in $(dokku_apps); do
cmd-traefik-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true
done
else
cmd-traefik-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT"
fi
}
cmd-traefik-report-single() {
declare APP="$1" INFO_FLAG="$2" FORMAT="${3:-stdout}"
if [[ "$INFO_FLAG" == "true" ]]; then
INFO_FLAG=""
fi
if [[ "$APP" != "--global" ]]; then
verify_app_name "$APP"
fi
local flag_map=(
"--traefik-computed-api-enabled: $(fn-traefik-computed-api-enabled)"
"--traefik-computed-api-entry-point: $(fn-traefik-computed-api-entry-point)"
"--traefik-computed-api-entry-point-address: $(fn-traefik-computed-api-entry-point-address)"
"--traefik-computed-api-vhost: $(fn-traefik-computed-api-vhost)"
"--traefik-computed-basic-auth-password: $(fn-traefik-computed-basic-auth-password)"
"--traefik-computed-basic-auth-username: $(fn-traefik-computed-basic-auth-username)"
"--traefik-computed-challenge-mode: $(fn-traefik-computed-challenge-mode)"
"--traefik-computed-dashboard-enabled: $(fn-traefik-computed-dashboard-enabled)"
"--traefik-computed-dns-provider: $(fn-traefik-computed-dns-provider)"
"--traefik-computed-http-entry-point: $(fn-traefik-computed-http-entry-point)"
"--traefik-computed-https-entry-point: $(fn-traefik-computed-https-entry-point)"
"--traefik-computed-image: $(fn-traefik-computed-image)"
"--traefik-computed-letsencrypt-email: $(fn-traefik-computed-letsencrypt-email)"
"--traefik-computed-letsencrypt-server: $(fn-traefik-computed-letsencrypt-server)"
"--traefik-computed-log-level: $(fn-traefik-computed-log-level)"
"--traefik-global-api-enabled: $(fn-traefik-global-api-enabled)"
"--traefik-global-api-entry-point: $(fn-traefik-global-api-entry-point)"
"--traefik-global-api-entry-point-address: $(fn-traefik-global-api-entry-point-address)"
"--traefik-global-api-vhost: $(fn-traefik-global-api-vhost)"
"--traefik-global-basic-auth-password: $(fn-traefik-global-basic-auth-password)"
"--traefik-global-basic-auth-username: $(fn-traefik-global-basic-auth-username)"
"--traefik-global-challenge-mode: $(fn-traefik-global-challenge-mode)"
"--traefik-global-dashboard-enabled: $(fn-traefik-global-dashboard-enabled)"
"--traefik-global-dns-provider: $(fn-traefik-global-dns-provider)"
"--traefik-global-http-entry-point: $(fn-traefik-global-http-entry-point)"
"--traefik-global-https-entry-point: $(fn-traefik-global-https-entry-point)"
"--traefik-global-image: $(fn-traefik-global-image)"
"--traefik-global-letsencrypt-email: $(fn-traefik-global-letsencrypt-email)"
"--traefik-global-letsencrypt-server: $(fn-traefik-global-letsencrypt-server)"
"--traefik-global-log-level: $(fn-traefik-global-log-level)"
)
local dns_provider_env_vars
dns_provider_env_vars="$(fn-traefik-dns-provider-env-vars)"
if [[ -n "$dns_provider_env_vars" ]]; then
while IFS= read -r line; do
local key value
key="$(echo "$line" | cut -d' ' -f1)"
value="$(echo "$line" | cut -d' ' -f2-)"
if [[ -n "$key" ]]; then
if [[ "$FORMAT" == "json" ]] || [[ "$INFO_FLAG" == "--traefik-${key}" ]]; then
flag_map+=("--traefik-${key}: ${value}")
else
flag_map+=("--traefik-${key}: *******")
fi
fi
done <<<"$dns_provider_env_vars"
fi
fn-report-validate-format "$FORMAT" "$INFO_FLAG"
if [[ "$FORMAT" == "json" ]]; then
fn-report-emit-json flag_map "traefik"
return
fi
if [[ -z "$INFO_FLAG" ]]; then
if [[ "$APP" == "--global" ]]; then
dokku_log_info2_quiet "global traefik information"
else
dokku_log_info2_quiet "${APP} traefik information"
fi
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
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
else
match=true
fi
fi
done
[[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}"
fi
}
cmd-traefik-logs() {
declare desc="display traefik logs from command line"
declare cmd="traefik:logs"

View File

@@ -0,0 +1,29 @@
module github.com/dokku/dokku/plugins/traefik-vhosts
go 1.26.2
require (
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
github.com/spf13/pflag v1.0.10
)
require (
github.com/alexellis/go-execute/v2 v2.2.1 // indirect
github.com/fatih/color v1.19.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.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/melbahja/goph v1.5.1 // 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.10 // indirect
github.com/ryanuber/columnize v2.1.2+incompatible // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/sys v0.46.0 // indirect
)
replace github.com/dokku/dokku/plugins/common => ../common

View File

@@ -0,0 +1,55 @@
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
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/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.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
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.5.1 h1:gdZyjn5nM6FO6mAZrv7XqE2gArQbb+V6FdIyH3Ru3vA=
github.com/melbahja/goph v1.5.1/go.mod h1:dDwo+44cmvfDLdiVpc6fJxexf5BA5yEDUeE5YgtuDO4=
github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I=
github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
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.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
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/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/traefik-vhosts/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-traefik-report-single "$@"

View File

@@ -0,0 +1,271 @@
package traefikvhosts
import (
"os"
"path/filepath"
"strings"
"github.com/dokku/dokku/plugins/common"
)
// ReportSingleApp is an internal function that displays the traefik report for one or more apps
func ReportSingleApp(appName string, format string, infoFlag string) error {
if appName != "--global" {
if err := common.VerifyAppName(appName); err != nil {
return err
}
}
flags := map[string]common.ReportFunc{
"--traefik-computed-api-enabled": reportComputedAPIEnabled,
"--traefik-computed-api-entry-point": reportComputedAPIEntryPoint,
"--traefik-computed-api-entry-point-address": reportComputedAPIEntryPointAddress,
"--traefik-computed-api-vhost": reportComputedAPIVhost,
"--traefik-computed-basic-auth-password": reportComputedBasicAuthPassword,
"--traefik-computed-basic-auth-username": reportComputedBasicAuthUsername,
"--traefik-computed-challenge-mode": reportComputedChallengeMode,
"--traefik-computed-dashboard-enabled": reportComputedDashboardEnabled,
"--traefik-computed-dns-provider": reportComputedDNSProvider,
"--traefik-computed-http-entry-point": reportComputedHTTPEntryPoint,
"--traefik-computed-https-entry-point": reportComputedHTTPSEntryPoint,
"--traefik-computed-image": reportComputedImage,
"--traefik-computed-letsencrypt-email": reportComputedLetsencryptEmail,
"--traefik-computed-letsencrypt-server": reportComputedLetsencryptServer,
"--traefik-computed-log-level": reportComputedLogLevel,
"--traefik-global-api-enabled": reportGlobalAPIEnabled,
"--traefik-global-api-entry-point": reportGlobalAPIEntryPoint,
"--traefik-global-api-entry-point-address": reportGlobalAPIEntryPointAddress,
"--traefik-global-api-vhost": reportGlobalAPIVhost,
"--traefik-global-basic-auth-password": reportGlobalBasicAuthPassword,
"--traefik-global-basic-auth-username": reportGlobalBasicAuthUsername,
"--traefik-global-challenge-mode": reportGlobalChallengeMode,
"--traefik-global-dashboard-enabled": reportGlobalDashboardEnabled,
"--traefik-global-dns-provider": reportGlobalDNSProvider,
"--traefik-global-http-entry-point": reportGlobalHTTPEntryPoint,
"--traefik-global-https-entry-point": reportGlobalHTTPSEntryPoint,
"--traefik-global-image": reportGlobalImage,
"--traefik-global-letsencrypt-email": reportGlobalLetsencryptEmail,
"--traefik-global-letsencrypt-server": reportGlobalLetsencryptServer,
"--traefik-global-log-level": reportGlobalLogLevel,
}
// dns-provider-* env vars are dynamic; their values are masked in the default
// stdout report, but shown for --format json or when queried explicitly by name
dnsProviderVars, err := common.PropertyGetAllByPrefix("traefik", "--global", "dns-provider-")
if err != nil {
return err
}
for key, value := range dnsProviderVars {
flagName := "--traefik-" + key
realValue := value
if format == "json" || infoFlag == flagName {
flags[flagName] = func(string) string { return realValue }
} else {
flags[flagName] = func(string) string { return "*******" }
}
}
flagKeys := []string{}
for flagKey := range flags {
flagKeys = append(flagKeys, flagKey)
}
infoFlags := common.CollectReport(appName, infoFlag, flags)
return common.ReportSingleApp(common.ReportSingleAppInput{
ReportType: "traefik",
AppName: appName,
InfoFlag: infoFlag,
InfoFlags: infoFlags,
InfoFlagKeys: flagKeys,
Format: format,
TrimPrefix: true,
UppercaseFirstCharacter: true,
EmitLegacyPrefix: false,
})
}
func reportGlobalAPIEnabled(appName string) string {
return common.PropertyGet("traefik", "--global", "api-enabled")
}
func reportComputedAPIEnabled(appName string) string {
value := reportGlobalAPIEnabled(appName)
if value == "" {
value = "false"
}
return value
}
func reportGlobalAPIEntryPoint(appName string) string {
return common.PropertyGet("traefik", "--global", "api-entry-point")
}
func reportComputedAPIEntryPoint(appName string) string {
return reportGlobalAPIEntryPoint(appName)
}
func reportGlobalAPIEntryPointAddress(appName string) string {
return common.PropertyGet("traefik", "--global", "api-entry-point-address")
}
func reportComputedAPIEntryPointAddress(appName string) string {
return reportGlobalAPIEntryPointAddress(appName)
}
func reportGlobalAPIVhost(appName string) string {
return common.PropertyGet("traefik", "--global", "api-vhost")
}
func reportComputedAPIVhost(appName string) string {
value := reportGlobalAPIVhost(appName)
if value == "" {
value = "traefik.dokku.me"
}
return value
}
func reportGlobalBasicAuthPassword(appName string) string {
return common.PropertyGet("traefik", "--global", "basic-auth-password")
}
func reportComputedBasicAuthPassword(appName string) string {
return reportGlobalBasicAuthPassword(appName)
}
func reportGlobalBasicAuthUsername(appName string) string {
return common.PropertyGet("traefik", "--global", "basic-auth-username")
}
func reportComputedBasicAuthUsername(appName string) string {
return reportGlobalBasicAuthUsername(appName)
}
func reportGlobalChallengeMode(appName string) string {
return common.PropertyGet("traefik", "--global", "challenge-mode")
}
func reportComputedChallengeMode(appName string) string {
value := reportGlobalChallengeMode(appName)
if value == "" {
value = "tls"
}
return value
}
func reportGlobalDashboardEnabled(appName string) string {
return common.PropertyGet("traefik", "--global", "dashboard-enabled")
}
func reportComputedDashboardEnabled(appName string) string {
value := reportGlobalDashboardEnabled(appName)
if value == "" {
value = "false"
}
return value
}
func reportGlobalDNSProvider(appName string) string {
return common.PropertyGet("traefik", "--global", "dns-provider")
}
func reportComputedDNSProvider(appName string) string {
return reportGlobalDNSProvider(appName)
}
func reportGlobalHTTPEntryPoint(appName string) string {
return common.PropertyGet("traefik", "--global", "http-entry-point")
}
func reportComputedHTTPEntryPoint(appName string) string {
value := reportGlobalHTTPEntryPoint(appName)
if value == "" {
value = "http"
}
return value
}
func reportGlobalHTTPSEntryPoint(appName string) string {
return common.PropertyGet("traefik", "--global", "https-entry-point")
}
func reportComputedHTTPSEntryPoint(appName string) string {
value := reportGlobalHTTPSEntryPoint(appName)
if value == "" {
value = "https"
}
return value
}
func reportGlobalImage(appName string) string {
return common.PropertyGet("traefik", "--global", "image")
}
func reportComputedImage(appName string) string {
value := reportGlobalImage(appName)
if value == "" {
value = dockerfileFromImage("traefik-vhosts")
}
return value
}
func reportGlobalLetsencryptEmail(appName string) string {
return common.PropertyGet("traefik", "--global", "letsencrypt-email")
}
func reportComputedLetsencryptEmail(appName string) string {
return reportGlobalLetsencryptEmail(appName)
}
func reportGlobalLetsencryptServer(appName string) string {
return common.PropertyGet("traefik", "--global", "letsencrypt-server")
}
func reportComputedLetsencryptServer(appName string) string {
value := reportGlobalLetsencryptServer(appName)
if value == "" {
value = "https://acme-v02.api.letsencrypt.org/directory"
}
return value
}
func reportGlobalLogLevel(appName string) string {
return common.PropertyGet("traefik", "--global", "log-level")
}
func reportComputedLogLevel(appName string) string {
value := reportGlobalLogLevel(appName)
if value == "" {
value = "ERROR"
}
return strings.ToUpper(value)
}
// dockerfileFromImage returns the image referenced by the FROM line of the named
// plugin's Dockerfile, or an empty string when it cannot be read
func dockerfileFromImage(pluginName string) string {
pluginPath := os.Getenv("PLUGIN_AVAILABLE_PATH")
data, err := os.ReadFile(filepath.Join(pluginPath, pluginName, "Dockerfile"))
if err != nil {
return ""
}
for _, line := range strings.Split(string(data), "\n") {
if strings.Contains(line, "FROM") {
fields := strings.Fields(line)
if len(fields) >= 2 {
return fields[1]
}
}
}
return ""
}

View File

@@ -0,0 +1,40 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
traefikvhosts "github.com/dokku/dokku/plugins/traefik-vhosts"
flag "github.com/spf13/pflag"
)
// main entrypoint to all subcommands
func main() {
parts := strings.Split(os.Args[0], "/")
subcommand := parts[len(parts)-1]
var err error
switch subcommand {
case "report":
args := flag.NewFlagSet("traefik-vhosts:report", flag.ExitOnError)
format := args.String("format", "stdout", "format: [ stdout | json ]")
reportArgs, flagErr := common.ParseReportArgs("traefik-vhosts", os.Args[2:])
if flagErr == nil {
args.Parse(reportArgs.OSArgs)
appName := args.Arg(0)
if reportArgs.IsGlobal {
appName = "--global"
}
err = traefikvhosts.CommandReport(appName, *format, reportArgs.InfoFlag)
}
default:
err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand)
}
if err != nil {
common.LogFailWithError(err)
}
}

View File

@@ -0,0 +1,31 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
traefikvhosts "github.com/dokku/dokku/plugins/traefik-vhosts"
)
// main entrypoint to all triggers
func main() {
parts := strings.Split(os.Args[0], "/")
trigger := parts[len(parts)-1]
flag.Parse()
var err error
switch trigger {
case "report":
appName := flag.Arg(0)
err = traefikvhosts.ReportSingleApp(appName, "", "")
default:
err = fmt.Errorf("Invalid plugin trigger call: %s", trigger)
}
if err != nil {
common.LogFailWithError(err)
}
}

View File

@@ -0,0 +1,29 @@
package traefikvhosts
import (
"errors"
"github.com/dokku/dokku/plugins/common"
)
// CommandReport displays a traefik-vhosts report for one or more apps
func CommandReport(appName string, format string, infoFlag string) error {
if len(appName) == 0 {
apps, err := common.DokkuApps()
if err != nil {
if errors.Is(err, common.NoAppsExist) {
common.LogWarn(err.Error())
return nil
}
return err
}
for _, appName := range apps {
if err := ReportSingleApp(appName, format, infoFlag); err != nil {
return err
}
}
return nil
}
return ReportSingleApp(appName, format, infoFlag)
}

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/traefik-vhosts/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-traefik-report "$@"

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env bats
load test_helper
setup() {
create_app
}
teardown() {
destroy_app
dokku caddy:set --global log-level >/dev/null 2>&1 || true
dokku caddy:set --global tls-internal >/dev/null 2>&1 || true
}
@test "(caddy:report) info-flag works before deploy" {
run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "false"
run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-invalid-flag"
echo "output: $output"
echo "status: $status"
assert_failure
assert_output_contains "Invalid flag passed"
}
@test "(caddy:report) --format json" {
run /bin/bash -c "dokku caddy:set --global log-level"
assert_success
run /bin/bash -c "dokku caddy:report --global --format json | jq -e ."
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku caddy:report --global --format json | jq -r '.\"computed-log-level\"'"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "ERROR"
run /bin/bash -c "dokku caddy:report --global"
echo "output: $output"
echo "status: $status"
assert_success
assert_output_contains "global caddy information"
}
@test "(caddy:report) tls-internal raw global computed" {
run /bin/bash -c "dokku caddy:set --global tls-internal"
assert_success
run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-tls-internal"
assert_success
assert_output ""
run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal"
assert_success
assert_output "false"
run /bin/bash -c "dokku caddy:set --global tls-internal true"
assert_success
run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-global-tls-internal"
assert_success
assert_output "true"
run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal"
assert_success
assert_output "true"
run /bin/bash -c "dokku caddy:set $TEST_APP tls-internal false"
assert_success
run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-tls-internal"
assert_success
assert_output "false"
run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal"
assert_success
assert_output "false"
}

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env bats
load test_helper
setup() {
create_app
}
teardown() {
destroy_app
dokku haproxy:set --global log-level >/dev/null 2>&1 || true
dokku haproxy:set --global refresh-conf >/dev/null 2>&1 || true
}
@test "(haproxy:report) info-flag works before deploy" {
run /bin/bash -c "dokku haproxy:set --global refresh-conf"
assert_success
run /bin/bash -c "dokku haproxy:report $TEST_APP --haproxy-computed-refresh-conf"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "10"
run /bin/bash -c "dokku haproxy:report $TEST_APP --haproxy-invalid-flag"
echo "output: $output"
echo "status: $status"
assert_failure
assert_output_contains "Invalid flag passed"
}
@test "(haproxy:report) --format json" {
run /bin/bash -c "dokku haproxy:set --global log-level"
assert_success
run /bin/bash -c "dokku haproxy:report --global --format json | jq -e ."
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku haproxy:report --global --format json | jq -r '.\"computed-log-level\"'"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "ERROR"
run /bin/bash -c "dokku haproxy:report --global"
echo "output: $output"
echo "status: $status"
assert_success
assert_output_contains "global haproxy information"
}
@test "(haproxy:report) refresh-conf global computed" {
run /bin/bash -c "dokku haproxy:set --global refresh-conf"
assert_success
run /bin/bash -c "dokku haproxy:report --global --haproxy-global-refresh-conf"
assert_success
assert_output ""
run /bin/bash -c "dokku haproxy:report --global --haproxy-computed-refresh-conf"
assert_success
assert_output "10"
run /bin/bash -c "dokku haproxy:set --global refresh-conf 30"
assert_success
run /bin/bash -c "dokku haproxy:report --global --haproxy-global-refresh-conf"
assert_success
assert_output "30"
run /bin/bash -c "dokku haproxy:report --global --haproxy-computed-refresh-conf"
assert_success
assert_output "30"
}

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env bats
load test_helper
setup() {
create_app
}
teardown() {
destroy_app
dokku openresty:set --global log-level >/dev/null 2>&1 || true
dokku openresty:set --global proxy-buffer-size >/dev/null 2>&1 || true
dokku openresty:set --global client-max-body-size >/dev/null 2>&1 || true
}
@test "(openresty:report) info-flag works before deploy" {
run /bin/bash -c "dokku openresty:set --global log-level"
assert_success
run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-log-level"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "ERROR"
run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-invalid-flag"
echo "output: $output"
echo "status: $status"
assert_failure
assert_output_contains "Invalid flag passed"
}
@test "(openresty:report) --format json" {
run /bin/bash -c "dokku openresty:set --global proxy-buffer-size"
assert_success
run /bin/bash -c "dokku openresty:report --global --format json | jq -e ."
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"computed-proxy-buffer-size\"'"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "4k"
run /bin/bash -c "dokku openresty:report --global"
echo "output: $output"
echo "status: $status"
assert_success
assert_output_contains "global openresty information"
}
@test "(openresty:report) client-max-body-size raw global computed" {
run /bin/bash -c "dokku openresty:set --global client-max-body-size"
assert_success
run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-client-max-body-size"
assert_success
assert_output ""
run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-client-max-body-size"
assert_success
assert_output "1m"
run /bin/bash -c "dokku openresty:set --global client-max-body-size 2m"
assert_success
run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-client-max-body-size"
assert_success
assert_output "2m"
run /bin/bash -c "dokku openresty:set $TEST_APP client-max-body-size 5m"
assert_success
run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-client-max-body-size"
assert_success
assert_output "5m"
run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-client-max-body-size"
assert_success
assert_output "5m"
}

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env bats
load test_helper
setup() {
create_app
}
teardown() {
destroy_app
dokku traefik:set --global challenge-mode >/dev/null 2>&1 || true
dokku traefik:set --global dns-provider-test_key >/dev/null 2>&1 || true
}
@test "(traefik:report) info-flag works before deploy" {
run /bin/bash -c "dokku traefik:set --global challenge-mode"
assert_success
run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-computed-challenge-mode"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "tls"
run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-invalid-flag"
echo "output: $output"
echo "status: $status"
assert_failure
assert_output_contains "Invalid flag passed"
}
@test "(traefik:report) --format json" {
run /bin/bash -c "dokku traefik:report --global --format json | jq -e ."
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku traefik:report --global --format json | jq -r '.\"computed-api-enabled\"'"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "false"
run /bin/bash -c "dokku traefik:report --global"
echo "output: $output"
echo "status: $status"
assert_success
assert_output_contains "global traefik information"
}
@test "(traefik:report) dns-provider values are masked unless queried" {
run /bin/bash -c "dokku traefik:set --global dns-provider-test_key secretvalue"
assert_success
run /bin/bash -c "dokku traefik:report --global | grep test_key"
echo "output: $output"
echo "status: $status"
assert_success
assert_output_contains "*******"
run /bin/bash -c "dokku traefik:report --global --traefik-dns-provider-test_key"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "secretvalue"
run /bin/bash -c "dokku traefik:report --global --format json | jq -r '.\"dns-provider-test_key\"'"
echo "output: $output"
echo "status: $status"
assert_success
assert_output "secretvalue"
}