diff --git a/go.work b/go.work index 931e6439d..faac91ae3 100644 --- a/go.work +++ b/go.work @@ -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 ) diff --git a/plugins/caddy-vhosts/.gitignore b/plugins/caddy-vhosts/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/caddy-vhosts/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/caddy-vhosts/Makefile b/plugins/caddy-vhosts/Makefile new file mode 100644 index 000000000..f0ee8b444 --- /dev/null +++ b/plugins/caddy-vhosts/Makefile @@ -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 diff --git a/plugins/caddy-vhosts/command-functions b/plugins/caddy-vhosts/command-functions old mode 100755 new mode 100644 index c13c65be1..d7db32ac1 --- a/plugins/caddy-vhosts/command-functions +++ b/plugins/caddy-vhosts/command-functions @@ -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" diff --git a/plugins/caddy-vhosts/go.mod b/plugins/caddy-vhosts/go.mod new file mode 100644 index 000000000..edcc274af --- /dev/null +++ b/plugins/caddy-vhosts/go.mod @@ -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 diff --git a/plugins/caddy-vhosts/go.sum b/plugins/caddy-vhosts/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/caddy-vhosts/go.sum @@ -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= diff --git a/plugins/caddy-vhosts/report b/plugins/caddy-vhosts/report deleted file mode 100755 index 3c95b9e35..000000000 --- a/plugins/caddy-vhosts/report +++ /dev/null @@ -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 "$@" diff --git a/plugins/caddy-vhosts/report.go b/plugins/caddy-vhosts/report.go new file mode 100644 index 000000000..74ed793be --- /dev/null +++ b/plugins/caddy-vhosts/report.go @@ -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 "" +} diff --git a/plugins/caddy-vhosts/src/subcommands/subcommands.go b/plugins/caddy-vhosts/src/subcommands/subcommands.go new file mode 100644 index 000000000..d19bd94c6 --- /dev/null +++ b/plugins/caddy-vhosts/src/subcommands/subcommands.go @@ -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) + } +} diff --git a/plugins/caddy-vhosts/src/triggers/triggers.go b/plugins/caddy-vhosts/src/triggers/triggers.go new file mode 100644 index 000000000..28e4810bd --- /dev/null +++ b/plugins/caddy-vhosts/src/triggers/triggers.go @@ -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) + } +} diff --git a/plugins/caddy-vhosts/subcommands.go b/plugins/caddy-vhosts/subcommands.go new file mode 100644 index 000000000..a3d3ae247 --- /dev/null +++ b/plugins/caddy-vhosts/subcommands.go @@ -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) +} diff --git a/plugins/caddy-vhosts/subcommands/report b/plugins/caddy-vhosts/subcommands/report deleted file mode 100755 index 32336e355..000000000 --- a/plugins/caddy-vhosts/subcommands/report +++ /dev/null @@ -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 "$@" diff --git a/plugins/haproxy-vhosts/.gitignore b/plugins/haproxy-vhosts/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/haproxy-vhosts/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/haproxy-vhosts/Makefile b/plugins/haproxy-vhosts/Makefile new file mode 100644 index 000000000..b727ed3b4 --- /dev/null +++ b/plugins/haproxy-vhosts/Makefile @@ -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 diff --git a/plugins/haproxy-vhosts/command-functions b/plugins/haproxy-vhosts/command-functions old mode 100755 new mode 100644 index 23e654591..e871ef232 --- a/plugins/haproxy-vhosts/command-functions +++ b/plugins/haproxy-vhosts/command-functions @@ -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" diff --git a/plugins/haproxy-vhosts/go.mod b/plugins/haproxy-vhosts/go.mod new file mode 100644 index 000000000..3384738c9 --- /dev/null +++ b/plugins/haproxy-vhosts/go.mod @@ -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 diff --git a/plugins/haproxy-vhosts/go.sum b/plugins/haproxy-vhosts/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/haproxy-vhosts/go.sum @@ -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= diff --git a/plugins/haproxy-vhosts/report b/plugins/haproxy-vhosts/report deleted file mode 100755 index 14b3ce537..000000000 --- a/plugins/haproxy-vhosts/report +++ /dev/null @@ -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 "$@" diff --git a/plugins/haproxy-vhosts/report.go b/plugins/haproxy-vhosts/report.go new file mode 100644 index 000000000..9f3645f91 --- /dev/null +++ b/plugins/haproxy-vhosts/report.go @@ -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 "" +} diff --git a/plugins/haproxy-vhosts/src/subcommands/subcommands.go b/plugins/haproxy-vhosts/src/subcommands/subcommands.go new file mode 100644 index 000000000..fc9df3b54 --- /dev/null +++ b/plugins/haproxy-vhosts/src/subcommands/subcommands.go @@ -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) + } +} diff --git a/plugins/haproxy-vhosts/src/triggers/triggers.go b/plugins/haproxy-vhosts/src/triggers/triggers.go new file mode 100644 index 000000000..ce8deb150 --- /dev/null +++ b/plugins/haproxy-vhosts/src/triggers/triggers.go @@ -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) + } +} diff --git a/plugins/haproxy-vhosts/subcommands.go b/plugins/haproxy-vhosts/subcommands.go new file mode 100644 index 000000000..0cc412822 --- /dev/null +++ b/plugins/haproxy-vhosts/subcommands.go @@ -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) +} diff --git a/plugins/haproxy-vhosts/subcommands/report b/plugins/haproxy-vhosts/subcommands/report deleted file mode 100755 index 0e483c438..000000000 --- a/plugins/haproxy-vhosts/subcommands/report +++ /dev/null @@ -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 "$@" diff --git a/plugins/openresty-vhosts/.gitignore b/plugins/openresty-vhosts/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/openresty-vhosts/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/openresty-vhosts/Makefile b/plugins/openresty-vhosts/Makefile new file mode 100644 index 000000000..b300b082d --- /dev/null +++ b/plugins/openresty-vhosts/Makefile @@ -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 diff --git a/plugins/openresty-vhosts/command-functions b/plugins/openresty-vhosts/command-functions old mode 100755 new mode 100644 index b4ec905f7..931e5aab6 --- a/plugins/openresty-vhosts/command-functions +++ b/plugins/openresty-vhosts/command-functions @@ -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" diff --git a/plugins/openresty-vhosts/go.mod b/plugins/openresty-vhosts/go.mod new file mode 100644 index 000000000..a8bdfc044 --- /dev/null +++ b/plugins/openresty-vhosts/go.mod @@ -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 diff --git a/plugins/openresty-vhosts/go.sum b/plugins/openresty-vhosts/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/openresty-vhosts/go.sum @@ -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= diff --git a/plugins/openresty-vhosts/report b/plugins/openresty-vhosts/report deleted file mode 100755 index 05081e23a..000000000 --- a/plugins/openresty-vhosts/report +++ /dev/null @@ -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 "$@" diff --git a/plugins/openresty-vhosts/report.go b/plugins/openresty-vhosts/report.go new file mode 100644 index 000000000..b169381d5 --- /dev/null +++ b/plugins/openresty-vhosts/report.go @@ -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 "" +} diff --git a/plugins/openresty-vhosts/src/subcommands/subcommands.go b/plugins/openresty-vhosts/src/subcommands/subcommands.go new file mode 100644 index 000000000..e80f15fa2 --- /dev/null +++ b/plugins/openresty-vhosts/src/subcommands/subcommands.go @@ -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) + } +} diff --git a/plugins/openresty-vhosts/src/triggers/triggers.go b/plugins/openresty-vhosts/src/triggers/triggers.go new file mode 100644 index 000000000..b4e7f6c8a --- /dev/null +++ b/plugins/openresty-vhosts/src/triggers/triggers.go @@ -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) + } +} diff --git a/plugins/openresty-vhosts/subcommands.go b/plugins/openresty-vhosts/subcommands.go new file mode 100644 index 000000000..ae49b54f9 --- /dev/null +++ b/plugins/openresty-vhosts/subcommands.go @@ -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) +} diff --git a/plugins/openresty-vhosts/subcommands/report b/plugins/openresty-vhosts/subcommands/report deleted file mode 100755 index 5bf0b5380..000000000 --- a/plugins/openresty-vhosts/subcommands/report +++ /dev/null @@ -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 "$@" diff --git a/plugins/traefik-vhosts/.gitignore b/plugins/traefik-vhosts/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/traefik-vhosts/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/traefik-vhosts/Makefile b/plugins/traefik-vhosts/Makefile new file mode 100644 index 000000000..e868730fe --- /dev/null +++ b/plugins/traefik-vhosts/Makefile @@ -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 diff --git a/plugins/traefik-vhosts/command-functions b/plugins/traefik-vhosts/command-functions old mode 100755 new mode 100644 index 2d742dde0..fd3d630b7 --- a/plugins/traefik-vhosts/command-functions +++ b/plugins/traefik-vhosts/command-functions @@ -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" diff --git a/plugins/traefik-vhosts/go.mod b/plugins/traefik-vhosts/go.mod new file mode 100644 index 000000000..0b69e56e2 --- /dev/null +++ b/plugins/traefik-vhosts/go.mod @@ -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 diff --git a/plugins/traefik-vhosts/go.sum b/plugins/traefik-vhosts/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/traefik-vhosts/go.sum @@ -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= diff --git a/plugins/traefik-vhosts/report b/plugins/traefik-vhosts/report deleted file mode 100755 index 286e1c76c..000000000 --- a/plugins/traefik-vhosts/report +++ /dev/null @@ -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 "$@" diff --git a/plugins/traefik-vhosts/report.go b/plugins/traefik-vhosts/report.go new file mode 100644 index 000000000..a17cd87d2 --- /dev/null +++ b/plugins/traefik-vhosts/report.go @@ -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 "" +} diff --git a/plugins/traefik-vhosts/src/subcommands/subcommands.go b/plugins/traefik-vhosts/src/subcommands/subcommands.go new file mode 100644 index 000000000..abf636b0d --- /dev/null +++ b/plugins/traefik-vhosts/src/subcommands/subcommands.go @@ -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) + } +} diff --git a/plugins/traefik-vhosts/src/triggers/triggers.go b/plugins/traefik-vhosts/src/triggers/triggers.go new file mode 100644 index 000000000..e068ec947 --- /dev/null +++ b/plugins/traefik-vhosts/src/triggers/triggers.go @@ -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) + } +} diff --git a/plugins/traefik-vhosts/subcommands.go b/plugins/traefik-vhosts/subcommands.go new file mode 100644 index 000000000..28cc1c4c4 --- /dev/null +++ b/plugins/traefik-vhosts/subcommands.go @@ -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) +} diff --git a/plugins/traefik-vhosts/subcommands/report b/plugins/traefik-vhosts/subcommands/report deleted file mode 100755 index 1f020013b..000000000 --- a/plugins/traefik-vhosts/subcommands/report +++ /dev/null @@ -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 "$@" diff --git a/tests/unit/caddy-vhosts.bats b/tests/unit/caddy-vhosts.bats new file mode 100644 index 000000000..1c3180359 --- /dev/null +++ b/tests/unit/caddy-vhosts.bats @@ -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" +} diff --git a/tests/unit/haproxy-vhosts.bats b/tests/unit/haproxy-vhosts.bats new file mode 100644 index 000000000..1c5a5f970 --- /dev/null +++ b/tests/unit/haproxy-vhosts.bats @@ -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" +} diff --git a/tests/unit/openresty-vhosts.bats b/tests/unit/openresty-vhosts.bats new file mode 100644 index 000000000..916edcf8a --- /dev/null +++ b/tests/unit/openresty-vhosts.bats @@ -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" +} diff --git a/tests/unit/traefik-vhosts.bats b/tests/unit/traefik-vhosts.bats new file mode 100644 index 000000000..b1d6a6e9f --- /dev/null +++ b/tests/unit/traefik-vhosts.bats @@ -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" +}