feat: port nginx :report subcommand to golang

The bash :report implementation for the nginx-vhosts plugin is replaced with a compiled golang binary that reuses the plugin's existing golang property getters, so every raw, global and computed key is unchanged while collection runs in parallel and json is marshalled directly. The global report keeps its existing behaviour of surfacing only the global keys.
This commit is contained in:
Jose Diaz-Gonzalez
2026-07-07 07:12:59 -04:00
parent fe11d508d3
commit 7ee882c4e7
9 changed files with 227 additions and 190 deletions

View File

@@ -1,2 +1,6 @@
/pagesize
/nginx-property
/report
/report-subcommand
/triggers
/subcommands/report

View File

@@ -1,7 +1,15 @@
GOARCH ?= amd64
BUILD = pagesize nginx-property
TRIGGERS = triggers/report
BUILD = pagesize nginx-property report-subcommand triggers
PLUGIN_NAME = nginx-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
clean-pagesize:
rm -rf pagesize

177
plugins/nginx-vhosts/command-functions Executable file → Normal file
View File

@@ -6,183 +6,6 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions"
source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/internal-functions"
cmd-nginx-report() {
declare desc="displays a nginx report for one or more apps"
declare cmd="nginx: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-nginx-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT"
elif [[ -z "$APP" ]]; then
for app in $(dokku_apps); do
cmd-nginx-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true
done
else
cmd-nginx-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT"
fi
}
cmd-nginx-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=(
"--nginx-access-log-format: $(fn-nginx-access-log-format "$APP")"
"--nginx-computed-access-log-format: $(fn-nginx-computed-access-log-format "$APP")"
"--nginx-global-access-log-format: $(fn-nginx-global-access-log-format "$APP")"
"--nginx-access-log-path: $(fn-nginx-access-log-path "$APP")"
"--nginx-computed-access-log-path: $(fn-nginx-computed-access-log-path "$APP")"
"--nginx-global-access-log-path: $(fn-nginx-global-access-log-path "$APP")"
"--nginx-bind-address-ipv4: $(fn-nginx-bind-address-ipv4 "$APP")"
"--nginx-computed-bind-address-ipv4: $(fn-nginx-computed-bind-address-ipv4 "$APP")"
"--nginx-global-bind-address-ipv4: $(fn-nginx-global-bind-address-ipv4 "$APP")"
"--nginx-bind-address-ipv6: $(fn-nginx-bind-address-ipv6 "$APP")"
"--nginx-computed-bind-address-ipv6: $(fn-nginx-computed-bind-address-ipv6 "$APP")"
"--nginx-global-bind-address-ipv6: $(fn-nginx-global-bind-address-ipv6 "$APP")"
"--nginx-client-body-timeout: $(fn-nginx-client-body-timeout "$APP")"
"--nginx-computed-client-body-timeout: $(fn-nginx-computed-client-body-timeout "$APP")"
"--nginx-global-client-body-timeout: $(fn-nginx-global-client-body-timeout "$APP")"
"--nginx-client-header-timeout: $(fn-nginx-client-header-timeout "$APP")"
"--nginx-computed-client-header-timeout: $(fn-nginx-computed-client-header-timeout "$APP")"
"--nginx-global-client-header-timeout: $(fn-nginx-global-client-header-timeout "$APP")"
"--nginx-client-max-body-size: $(fn-nginx-client-max-body-size "$APP")"
"--nginx-computed-client-max-body-size: $(fn-nginx-computed-client-max-body-size "$APP")"
"--nginx-global-client-max-body-size: $(fn-nginx-global-client-max-body-size "$APP")"
"--nginx-disable-custom-config: $(fn-nginx-disable-custom-config "$APP")"
"--nginx-computed-disable-custom-config: $(fn-nginx-computed-disable-custom-config "$APP")"
"--nginx-global-disable-custom-config: $(fn-nginx-global-disable-custom-config "$APP")"
"--nginx-error-log-path: $(fn-nginx-error-log-path "$APP")"
"--nginx-computed-error-log-path: $(fn-nginx-computed-error-log-path "$APP")"
"--nginx-global-error-log-path: $(fn-nginx-global-error-log-path "$APP")"
"--nginx-hsts-include-subdomains: $(fn-nginx-hsts-include-subdomains "$APP")"
"--nginx-computed-hsts-include-subdomains: $(fn-nginx-computed-hsts-include-subdomains "$APP")"
"--nginx-global-hsts-include-subdomains: $(fn-nginx-global-hsts-include-subdomains "$APP")"
"--nginx-hsts-max-age: $(fn-nginx-hsts-max-age "$APP")"
"--nginx-computed-hsts-max-age: $(fn-nginx-computed-hsts-max-age "$APP")"
"--nginx-global-hsts-max-age: $(fn-nginx-global-hsts-max-age "$APP")"
"--nginx-hsts-preload: $(fn-nginx-hsts-preload "$APP")"
"--nginx-computed-hsts-preload: $(fn-nginx-computed-hsts-preload "$APP")"
"--nginx-global-hsts-preload: $(fn-nginx-global-hsts-preload "$APP")"
"--nginx-hsts: $(fn-nginx-hsts "$APP")"
"--nginx-computed-hsts: $(fn-nginx-computed-hsts "$APP")"
"--nginx-global-hsts: $(fn-nginx-global-hsts "$APP")"
"--nginx-last-visited-at: $(fn-nginx-vhosts-last-visited-at "$APP")"
"--nginx-keepalive-timeout: $(fn-nginx-keepalive-timeout "$APP")"
"--nginx-computed-keepalive-timeout: $(fn-nginx-computed-keepalive-timeout "$APP")"
"--nginx-global-keepalive-timeout: $(fn-nginx-global-keepalive-timeout "$APP")"
"--nginx-lingering-timeout: $(fn-nginx-lingering-timeout "$APP")"
"--nginx-computed-lingering-timeout: $(fn-nginx-computed-lingering-timeout "$APP")"
"--nginx-global-lingering-timeout: $(fn-nginx-global-lingering-timeout "$APP")"
"--nginx-nginx-conf-sigil-path: $(fn-nginx-nginx-conf-sigil-path "$APP")"
"--nginx-computed-nginx-conf-sigil-path: $(fn-nginx-computed-nginx-conf-sigil-path "$APP")"
"--nginx-global-nginx-conf-sigil-path: $(fn-nginx-global-nginx-conf-sigil-path "$APP")"
"--nginx-nginx-service-command: $(fn-nginx-nginx-service-command "$APP")"
"--nginx-computed-nginx-service-command: $(fn-nginx-computed-nginx-service-command "$APP")"
"--nginx-global-nginx-service-command: $(fn-nginx-global-nginx-service-command "$APP")"
"--nginx-proxy-buffer-size: $(fn-nginx-proxy-buffer-size "$APP")"
"--nginx-computed-proxy-buffer-size: $(fn-nginx-computed-proxy-buffer-size "$APP")"
"--nginx-global-proxy-buffer-size: $(fn-nginx-global-proxy-buffer-size "$APP")"
"--nginx-proxy-buffering: $(fn-nginx-proxy-buffering "$APP")"
"--nginx-computed-proxy-buffering: $(fn-nginx-computed-proxy-buffering "$APP")"
"--nginx-global-proxy-buffering: $(fn-nginx-global-proxy-buffering "$APP")"
"--nginx-proxy-buffers: $(fn-nginx-proxy-buffers "$APP")"
"--nginx-computed-proxy-buffers: $(fn-nginx-computed-proxy-buffers "$APP")"
"--nginx-global-proxy-buffers: $(fn-nginx-global-proxy-buffers "$APP")"
"--nginx-proxy-busy-buffers-size: $(fn-nginx-proxy-busy-buffers-size "$APP")"
"--nginx-computed-proxy-busy-buffers-size: $(fn-nginx-computed-proxy-busy-buffers-size "$APP")"
"--nginx-global-proxy-busy-buffers-size: $(fn-nginx-global-proxy-busy-buffers-size "$APP")"
"--nginx-proxy-connect-timeout: $(fn-nginx-proxy-connect-timeout "$APP")"
"--nginx-computed-proxy-connect-timeout: $(fn-nginx-computed-proxy-connect-timeout "$APP")"
"--nginx-global-proxy-connect-timeout: $(fn-nginx-global-proxy-connect-timeout "$APP")"
"--nginx-proxy-read-timeout: $(fn-nginx-proxy-read-timeout "$APP")"
"--nginx-computed-proxy-read-timeout: $(fn-nginx-computed-proxy-read-timeout "$APP")"
"--nginx-global-proxy-read-timeout: $(fn-nginx-global-proxy-read-timeout "$APP")"
"--nginx-proxy-send-timeout: $(fn-nginx-proxy-send-timeout "$APP")"
"--nginx-computed-proxy-send-timeout: $(fn-nginx-computed-proxy-send-timeout "$APP")"
"--nginx-global-proxy-send-timeout: $(fn-nginx-global-proxy-send-timeout "$APP")"
"--nginx-send-timeout: $(fn-nginx-send-timeout "$APP")"
"--nginx-computed-send-timeout: $(fn-nginx-computed-send-timeout "$APP")"
"--nginx-global-send-timeout: $(fn-nginx-global-send-timeout "$APP")"
"--nginx-proxy-keepalive: $(fn-nginx-proxy-keepalive "$APP")"
"--nginx-computed-proxy-keepalive: $(fn-nginx-computed-proxy-keepalive "$APP")"
"--nginx-global-proxy-keepalive: $(fn-nginx-global-proxy-keepalive "$APP")"
"--nginx-underscore-in-headers: $(fn-nginx-underscore-in-headers "$APP")"
"--nginx-computed-underscore-in-headers: $(fn-nginx-computed-underscore-in-headers "$APP")"
"--nginx-global-underscore-in-headers: $(fn-nginx-global-underscore-in-headers "$APP")"
"--nginx-x-forwarded-for-value: $(fn-nginx-x-forwarded-for-value "$APP")"
"--nginx-computed-x-forwarded-for-value: $(fn-nginx-computed-x-forwarded-for-value "$APP")"
"--nginx-global-x-forwarded-for-value: $(fn-nginx-global-x-forwarded-for-value "$APP")"
"--nginx-x-forwarded-port-value: $(fn-nginx-x-forwarded-port-value "$APP")"
"--nginx-computed-x-forwarded-port-value: $(fn-nginx-computed-x-forwarded-port-value "$APP")"
"--nginx-global-x-forwarded-port-value: $(fn-nginx-global-x-forwarded-port-value "$APP")"
"--nginx-x-forwarded-proto-value: $(fn-nginx-x-forwarded-proto-value "$APP")"
"--nginx-computed-x-forwarded-proto-value: $(fn-nginx-computed-x-forwarded-proto-value "$APP")"
"--nginx-global-x-forwarded-proto-value: $(fn-nginx-global-x-forwarded-proto-value "$APP")"
"--nginx-x-forwarded-ssl: $(fn-nginx-x-forwarded-ssl "$APP")"
"--nginx-computed-x-forwarded-ssl: $(fn-nginx-computed-x-forwarded-ssl "$APP")"
"--nginx-global-x-forwarded-ssl: $(fn-nginx-global-x-forwarded-ssl "$APP")"
)
if [[ "$APP" == "--global" ]]; then
fn-report-filter-global flag_map
fi
fn-report-validate-format "$FORMAT" "$INFO_FLAG"
if [[ "$FORMAT" == "json" ]]; then
fn-report-emit-json flag_map "nginx"
return
fi
if [[ -z "$INFO_FLAG" ]]; then
if [[ "$APP" == "--global" ]]; then
dokku_log_info2_quiet "global nginx information"
else
dokku_log_info2_quiet "${APP} nginx 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-nginx-show-config() {
declare desc="display app nginx config"
declare cmd="nginx:show-config"

View File

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

View File

@@ -0,0 +1,114 @@
package nginxvhosts
import (
"os"
"strconv"
"strings"
"github.com/dokku/dokku/plugins/common"
)
type nginxReportProp struct {
name string
appFn common.ReportFunc
computedFn common.ReportFunc
globalFn common.ReportFunc
}
func wrapGlobal(fn func() string) common.ReportFunc {
return func(string) string { return fn() }
}
func nginxReportProps() []nginxReportProp {
return []nginxReportProp{
{"access-log-format", AppAccessLogFormat, ComputedAccessLogFormat, wrapGlobal(GlobalAccessLogFormat)},
{"access-log-path", AppAccessLogPath, ComputedAccessLogPath, wrapGlobal(GlobalAccessLogPath)},
{"bind-address-ipv4", AppBindAddressIPv4, ComputedBindAddressIPv4, wrapGlobal(GlobalBindAddressIPv4)},
{"bind-address-ipv6", AppBindAddressIPv6, ComputedBindAddressIPv6, wrapGlobal(GlobalBindAddressIPv6)},
{"client-body-timeout", AppClientBodyTimeout, ComputedClientBodyTimeout, wrapGlobal(GlobalClientBodyTimeout)},
{"client-header-timeout", AppClientHeaderTimeout, ComputedClientHeaderTimeout, wrapGlobal(GlobalClientHeaderTimeout)},
{"client-max-body-size", AppClientMaxBodySize, ComputedClientMaxBodySize, wrapGlobal(GlobalClientMaxBodySize)},
{"disable-custom-config", AppDisableCustomConfig, ComputedDisableCustomConfig, wrapGlobal(GlobalDisableCustomConfig)},
{"error-log-path", AppErrorLogPath, ComputedErrorLogPath, wrapGlobal(GlobalErrorLogPath)},
{"hsts-include-subdomains", AppHSTSIncludeSubdomains, ComputedHSTSIncludeSubdomains, wrapGlobal(GlobalHSTSIncludeSubdomains)},
{"hsts-max-age", AppHSTSMaxAge, ComputedHSTSMaxAge, wrapGlobal(GlobalHSTSMaxAge)},
{"hsts-preload", AppHSTSPreload, ComputedHSTSPreload, wrapGlobal(GlobalHSTSPreload)},
{"hsts", AppHSTS, ComputedHSTS, wrapGlobal(GlobalHSTS)},
{"keepalive-timeout", AppKeepaliveTimeout, ComputedKeepaliveTimeout, wrapGlobal(GlobalKeepaliveTimeout)},
{"lingering-timeout", AppLingeringTimeout, ComputedLingeringTimeout, wrapGlobal(GlobalLingeringTimeout)},
{"nginx-conf-sigil-path", AppNginxConfSigilPath, ComputedNginxConfSigilPath, wrapGlobal(GlobalNginxConfSigilPath)},
{"nginx-service-command", AppNginxServiceCommand, ComputedNginxServiceCommand, wrapGlobal(GlobalNginxServiceCommand)},
{"proxy-buffer-size", AppProxyBufferSize, ComputedProxyBufferSize, wrapGlobal(GlobalProxyBufferSize)},
{"proxy-buffering", AppProxyBuffering, ComputedProxyBuffering, wrapGlobal(GlobalProxyBuffering)},
{"proxy-buffers", AppProxyBuffers, ComputedProxyBuffers, wrapGlobal(GlobalProxyBuffers)},
{"proxy-busy-buffers-size", AppProxyBusyBuffersSize, ComputedProxyBusyBuffersSize, wrapGlobal(GlobalProxyBusyBuffersSize)},
{"proxy-connect-timeout", AppProxyConnectTimeout, ComputedProxyConnectTimeout, wrapGlobal(GlobalProxyConnectTimeout)},
{"proxy-keepalive", AppProxyKeepalive, ComputedProxyKeepalive, wrapGlobal(GlobalProxyKeepalive)},
{"proxy-read-timeout", AppProxyReadTimeout, ComputedProxyReadTimeout, wrapGlobal(GlobalProxyReadTimeout)},
{"proxy-send-timeout", AppProxySendTimeout, ComputedProxySendTimeout, wrapGlobal(GlobalProxySendTimeout)},
{"send-timeout", AppSendTimeout, ComputedSendTimeout, wrapGlobal(GlobalSendTimeout)},
{"underscore-in-headers", AppUnderscoreInHeaders, ComputedUnderscoreInHeaders, wrapGlobal(GlobalUnderscoreInHeaders)},
{"x-forwarded-for-value", AppXForwardedForValue, ComputedXForwardedForValue, wrapGlobal(GlobalXForwardedForValue)},
{"x-forwarded-port-value", AppXForwardedPortValue, ComputedXForwardedPortValue, wrapGlobal(GlobalXForwardedPortValue)},
{"x-forwarded-proto-value", AppXForwardedProtoValue, ComputedXForwardedProtoValue, wrapGlobal(GlobalXForwardedProtoValue)},
{"x-forwarded-ssl", AppXForwardedSSL, ComputedXForwardedSSL, wrapGlobal(GlobalXForwardedSSL)},
}
}
// ReportSingleApp is an internal function that displays the nginx 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 nginxReportProps() {
flags["--nginx-"+p.name] = p.appFn
flags["--nginx-computed-"+p.name] = p.computedFn
flags["--nginx-global-"+p.name] = p.globalFn
}
flags["--nginx-last-visited-at"] = reportLastVisitedAt
if isGlobal {
for key := range flags {
if !strings.Contains(key, "-global-") {
delete(flags, key)
}
}
}
flagKeys := []string{}
for flagKey := range flags {
flagKeys = append(flagKeys, flagKey)
}
infoFlags := common.CollectReport(appName, infoFlag, flags)
return common.ReportSingleApp(common.ReportSingleAppInput{
ReportType: "nginx",
AppName: appName,
InfoFlag: infoFlag,
InfoFlags: infoFlags,
InfoFlagKeys: flagKeys,
Format: format,
TrimPrefix: true,
UppercaseFirstCharacter: true,
EmitLegacyPrefix: false,
})
}
func reportLastVisitedAt(appName string) string {
logPath := AppAccessLogPath(appName)
if logPath == "off" || logPath == "/dev/null" {
return ""
}
info, err := os.Stat(logPath)
if err != nil || !info.Mode().IsRegular() {
return ""
}
return strconv.FormatInt(info.ModTime().Unix(), 10)
}

View File

@@ -0,0 +1,40 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
nginxvhosts "github.com/dokku/dokku/plugins/nginx-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("nginx-vhosts:report", flag.ExitOnError)
format := args.String("format", "stdout", "format: [ stdout | json ]")
reportArgs, flagErr := common.ParseReportArgs("nginx-vhosts", os.Args[2:])
if flagErr == nil {
args.Parse(reportArgs.OSArgs)
appName := args.Arg(0)
if reportArgs.IsGlobal {
appName = "--global"
}
err = nginxvhosts.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"
nginxvhosts "github.com/dokku/dokku/plugins/nginx-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 = nginxvhosts.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 nginxvhosts
import (
"errors"
"github.com/dokku/dokku/plugins/common"
)
// CommandReport displays an nginx 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/nginx-vhosts/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-nginx-report "$@"