From f9db9583a187f1be12289dd6201acbed15cfd5cf Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 7 Jul 2026 06:11:13 -0400 Subject: [PATCH 1/9] feat: port builder :report subcommands to golang The bash :report implementations for the dockerfile, herokuish, lambda, nixpacks, pack, and railpack builders are replaced with a compiled golang binary that collects report keys in parallel and marshals json directly, avoiding the per-key subshell and jq forks that made the bash reports slow. The subcommands/report and root report trigger become symlinks to the compiled binary, while the non-report bash helpers each plugin still relies on are left in place. --- go.work | 6 ++ plugins/builder-dockerfile/.gitignore | 4 + plugins/builder-dockerfile/Makefile | 13 +++ plugins/builder-dockerfile/go.mod | 29 +++++++ plugins/builder-dockerfile/go.sum | 55 ++++++++++++ plugins/builder-dockerfile/internal-functions | 87 ------------------- plugins/builder-dockerfile/report | 6 -- plugins/builder-dockerfile/report.go | 66 ++++++++++++++ .../src/subcommands/subcommands.go | 40 +++++++++ .../src/triggers/triggers.go | 31 +++++++ plugins/builder-dockerfile/subcommands.go | 29 +++++++ plugins/builder-dockerfile/subcommands/report | 6 -- plugins/builder-herokuish/.gitignore | 4 + plugins/builder-herokuish/Makefile | 13 +++ plugins/builder-herokuish/go.mod | 29 +++++++ plugins/builder-herokuish/go.sum | 55 ++++++++++++ plugins/builder-herokuish/internal-functions | 87 ------------------- plugins/builder-herokuish/report | 6 -- plugins/builder-herokuish/report.go | 83 ++++++++++++++++++ .../src/subcommands/subcommands.go | 40 +++++++++ .../src/triggers/triggers.go | 31 +++++++ plugins/builder-herokuish/subcommands.go | 29 +++++++ plugins/builder-herokuish/subcommands/report | 6 -- plugins/builder-lambda/.gitignore | 4 + plugins/builder-lambda/Makefile | 13 +++ plugins/builder-lambda/go.mod | 29 +++++++ plugins/builder-lambda/go.sum | 55 ++++++++++++ plugins/builder-lambda/internal-functions | 87 ------------------- plugins/builder-lambda/report | 6 -- plugins/builder-lambda/report.go | 66 ++++++++++++++ .../src/subcommands/subcommands.go | 40 +++++++++ .../builder-lambda/src/triggers/triggers.go | 31 +++++++ plugins/builder-lambda/subcommands.go | 29 +++++++ plugins/builder-lambda/subcommands/report | 6 -- plugins/builder-nixpacks/.gitignore | 4 + plugins/builder-nixpacks/Makefile | 13 +++ plugins/builder-nixpacks/go.mod | 29 +++++++ plugins/builder-nixpacks/go.sum | 55 ++++++++++++ plugins/builder-nixpacks/internal-functions | 87 ------------------- plugins/builder-nixpacks/report | 6 -- plugins/builder-nixpacks/report.go | 66 ++++++++++++++ .../src/subcommands/subcommands.go | 40 +++++++++ .../builder-nixpacks/src/triggers/triggers.go | 31 +++++++ plugins/builder-nixpacks/subcommands.go | 29 +++++++ plugins/builder-nixpacks/subcommands/report | 6 -- plugins/builder-pack/.gitignore | 4 + plugins/builder-pack/Makefile | 13 +++ plugins/builder-pack/go.mod | 29 +++++++ plugins/builder-pack/go.sum | 55 ++++++++++++ plugins/builder-pack/internal-functions | 87 ------------------- plugins/builder-pack/report | 6 -- plugins/builder-pack/report.go | 66 ++++++++++++++ .../src/subcommands/subcommands.go | 40 +++++++++ plugins/builder-pack/src/triggers/triggers.go | 31 +++++++ plugins/builder-pack/subcommands.go | 29 +++++++ plugins/builder-pack/subcommands/report | 6 -- plugins/builder-railpack/.gitignore | 4 + plugins/builder-railpack/Makefile | 13 +++ plugins/builder-railpack/go.mod | 29 +++++++ plugins/builder-railpack/go.sum | 55 ++++++++++++ plugins/builder-railpack/internal-functions | 87 ------------------- plugins/builder-railpack/report | 6 -- plugins/builder-railpack/report.go | 66 ++++++++++++++ .../src/subcommands/subcommands.go | 40 +++++++++ .../builder-railpack/src/triggers/triggers.go | 31 +++++++ plugins/builder-railpack/subcommands.go | 29 +++++++ plugins/builder-railpack/subcommands/report | 6 -- 67 files changed, 1625 insertions(+), 594 deletions(-) create mode 100644 plugins/builder-dockerfile/.gitignore create mode 100644 plugins/builder-dockerfile/Makefile create mode 100644 plugins/builder-dockerfile/go.mod create mode 100644 plugins/builder-dockerfile/go.sum delete mode 100755 plugins/builder-dockerfile/report create mode 100644 plugins/builder-dockerfile/report.go create mode 100644 plugins/builder-dockerfile/src/subcommands/subcommands.go create mode 100644 plugins/builder-dockerfile/src/triggers/triggers.go create mode 100644 plugins/builder-dockerfile/subcommands.go delete mode 100755 plugins/builder-dockerfile/subcommands/report create mode 100644 plugins/builder-herokuish/.gitignore create mode 100644 plugins/builder-herokuish/Makefile create mode 100644 plugins/builder-herokuish/go.mod create mode 100644 plugins/builder-herokuish/go.sum mode change 100755 => 100644 plugins/builder-herokuish/internal-functions delete mode 100755 plugins/builder-herokuish/report create mode 100644 plugins/builder-herokuish/report.go create mode 100644 plugins/builder-herokuish/src/subcommands/subcommands.go create mode 100644 plugins/builder-herokuish/src/triggers/triggers.go create mode 100644 plugins/builder-herokuish/subcommands.go delete mode 100755 plugins/builder-herokuish/subcommands/report create mode 100644 plugins/builder-lambda/.gitignore create mode 100644 plugins/builder-lambda/Makefile create mode 100644 plugins/builder-lambda/go.mod create mode 100644 plugins/builder-lambda/go.sum mode change 100755 => 100644 plugins/builder-lambda/internal-functions delete mode 100755 plugins/builder-lambda/report create mode 100644 plugins/builder-lambda/report.go create mode 100644 plugins/builder-lambda/src/subcommands/subcommands.go create mode 100644 plugins/builder-lambda/src/triggers/triggers.go create mode 100644 plugins/builder-lambda/subcommands.go delete mode 100755 plugins/builder-lambda/subcommands/report create mode 100644 plugins/builder-nixpacks/.gitignore create mode 100644 plugins/builder-nixpacks/Makefile create mode 100644 plugins/builder-nixpacks/go.mod create mode 100644 plugins/builder-nixpacks/go.sum mode change 100755 => 100644 plugins/builder-nixpacks/internal-functions delete mode 100755 plugins/builder-nixpacks/report create mode 100644 plugins/builder-nixpacks/report.go create mode 100644 plugins/builder-nixpacks/src/subcommands/subcommands.go create mode 100644 plugins/builder-nixpacks/src/triggers/triggers.go create mode 100644 plugins/builder-nixpacks/subcommands.go delete mode 100755 plugins/builder-nixpacks/subcommands/report create mode 100644 plugins/builder-pack/.gitignore create mode 100644 plugins/builder-pack/Makefile create mode 100644 plugins/builder-pack/go.mod create mode 100644 plugins/builder-pack/go.sum mode change 100755 => 100644 plugins/builder-pack/internal-functions delete mode 100755 plugins/builder-pack/report create mode 100644 plugins/builder-pack/report.go create mode 100644 plugins/builder-pack/src/subcommands/subcommands.go create mode 100644 plugins/builder-pack/src/triggers/triggers.go create mode 100644 plugins/builder-pack/subcommands.go delete mode 100755 plugins/builder-pack/subcommands/report create mode 100644 plugins/builder-railpack/.gitignore create mode 100644 plugins/builder-railpack/Makefile create mode 100644 plugins/builder-railpack/go.mod create mode 100644 plugins/builder-railpack/go.sum mode change 100755 => 100644 plugins/builder-railpack/internal-functions delete mode 100755 plugins/builder-railpack/report create mode 100644 plugins/builder-railpack/report.go create mode 100644 plugins/builder-railpack/src/subcommands/subcommands.go create mode 100644 plugins/builder-railpack/src/triggers/triggers.go create mode 100644 plugins/builder-railpack/subcommands.go delete mode 100755 plugins/builder-railpack/subcommands/report diff --git a/go.work b/go.work index bf66069d0..931e6439d 100644 --- a/go.work +++ b/go.work @@ -4,6 +4,12 @@ use ( ./plugins/app-json ./plugins/apps ./plugins/builder + ./plugins/builder-dockerfile + ./plugins/builder-herokuish + ./plugins/builder-lambda + ./plugins/builder-nixpacks + ./plugins/builder-pack + ./plugins/builder-railpack ./plugins/buildpacks ./plugins/common ./plugins/config diff --git a/plugins/builder-dockerfile/.gitignore b/plugins/builder-dockerfile/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/builder-dockerfile/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/builder-dockerfile/Makefile b/plugins/builder-dockerfile/Makefile new file mode 100644 index 000000000..be9dbcce5 --- /dev/null +++ b/plugins/builder-dockerfile/Makefile @@ -0,0 +1,13 @@ +GOARCH ?= amd64 +TRIGGERS = triggers/report +BUILD = report-subcommand triggers +PLUGIN_NAME = builder-dockerfile + +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/builder-dockerfile/go.mod b/plugins/builder-dockerfile/go.mod new file mode 100644 index 000000000..dd1f20cb8 --- /dev/null +++ b/plugins/builder-dockerfile/go.mod @@ -0,0 +1,29 @@ +module github.com/dokku/dokku/plugins/builder-dockerfile + +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/builder-dockerfile/go.sum b/plugins/builder-dockerfile/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/builder-dockerfile/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/builder-dockerfile/internal-functions b/plugins/builder-dockerfile/internal-functions index a7c6a236a..66ecb200e 100755 --- a/plugins/builder-dockerfile/internal-functions +++ b/plugins/builder-dockerfile/internal-functions @@ -4,93 +4,6 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" set -eo pipefail [[ $DOKKU_TRACE ]] && set -x -cmd-builder-dockerfile-report() { - declare desc="displays a builder-dockerfile report for one or more apps" - declare cmd="builder-dockerfile: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-builder-dockerfile-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - elif [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-builder-dockerfile-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true - done - else - cmd-builder-dockerfile-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - fi -} - -cmd-builder-dockerfile-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=( - "--builder-dockerfile-computed-dockerfile-path: $(fn-builder-dockerfile-computed-dockerfile-path "$APP")" - "--builder-dockerfile-global-dockerfile-path: $(fn-builder-dockerfile-global-dockerfile-path "$APP")" - ) - else - verify_app_name "$APP" - flag_map=( - "--builder-dockerfile-computed-dockerfile-path: $(fn-builder-dockerfile-computed-dockerfile-path "$APP")" - "--builder-dockerfile-global-dockerfile-path: $(fn-builder-dockerfile-global-dockerfile-path "$APP")" - "--builder-dockerfile-dockerfile-path: $(fn-builder-dockerfile-dockerfile-path "$APP")" - ) - fi - - fn-report-validate-format "$FORMAT" "$INFO_FLAG" - - if [[ "$FORMAT" == "json" ]]; then - fn-report-emit-json flag_map "builder-dockerfile" - return - fi - - if [[ -z "$INFO_FLAG" ]]; then - if [[ "$APP" == "--global" ]]; then - dokku_log_info2_quiet "global builder-dockerfile information" - else - dokku_log_info2_quiet "${APP} builder-dockerfile 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 -} - fn-builder-dockerfile-computed-dockerfile-path() { declare APP="$1" diff --git a/plugins/builder-dockerfile/report b/plugins/builder-dockerfile/report deleted file mode 100755 index 1f809ffca..000000000 --- a/plugins/builder-dockerfile/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/builder-dockerfile/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-builder-dockerfile-report-single "$@" diff --git a/plugins/builder-dockerfile/report.go b/plugins/builder-dockerfile/report.go new file mode 100644 index 000000000..6e59cd31b --- /dev/null +++ b/plugins/builder-dockerfile/report.go @@ -0,0 +1,66 @@ +package builderdockerfile + +import ( + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the builder-dockerfile 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 + } + } + + var flags map[string]common.ReportFunc + if appName == "--global" { + flags = map[string]common.ReportFunc{ + "--builder-dockerfile-computed-dockerfile-path": reportComputedDockerfilePath, + "--builder-dockerfile-global-dockerfile-path": reportGlobalDockerfilePath, + } + } else { + flags = map[string]common.ReportFunc{ + "--builder-dockerfile-computed-dockerfile-path": reportComputedDockerfilePath, + "--builder-dockerfile-global-dockerfile-path": reportGlobalDockerfilePath, + "--builder-dockerfile-dockerfile-path": reportDockerfilePath, + } + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "builder-dockerfile", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: false, + }) +} + +func reportDockerfilePath(appName string) string { + return common.PropertyGet("builder-dockerfile", appName, "dockerfile-path") +} + +func reportGlobalDockerfilePath(appName string) string { + return common.PropertyGet("builder-dockerfile", "--global", "dockerfile-path") +} + +func reportComputedDockerfilePath(appName string) string { + value := reportDockerfilePath(appName) + if value == "" { + value = reportGlobalDockerfilePath(appName) + } + if value == "" { + value = "Dockerfile" + } + + return value +} diff --git a/plugins/builder-dockerfile/src/subcommands/subcommands.go b/plugins/builder-dockerfile/src/subcommands/subcommands.go new file mode 100644 index 000000000..df2ec7bd0 --- /dev/null +++ b/plugins/builder-dockerfile/src/subcommands/subcommands.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + builderdockerfile "github.com/dokku/dokku/plugins/builder-dockerfile" + "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("builder-dockerfile:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + reportArgs, flagErr := common.ParseReportArgs("builder-dockerfile", os.Args[2:]) + if flagErr == nil { + args.Parse(reportArgs.OSArgs) + appName := args.Arg(0) + if reportArgs.IsGlobal { + appName = "--global" + } + err = builderdockerfile.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/builder-dockerfile/src/triggers/triggers.go b/plugins/builder-dockerfile/src/triggers/triggers.go new file mode 100644 index 000000000..7e39978b5 --- /dev/null +++ b/plugins/builder-dockerfile/src/triggers/triggers.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + builderdockerfile "github.com/dokku/dokku/plugins/builder-dockerfile" + "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 = builderdockerfile.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/builder-dockerfile/subcommands.go b/plugins/builder-dockerfile/subcommands.go new file mode 100644 index 000000000..12e3827d8 --- /dev/null +++ b/plugins/builder-dockerfile/subcommands.go @@ -0,0 +1,29 @@ +package builderdockerfile + +import ( + "errors" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandReport displays a builder-dockerfile 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/builder-dockerfile/subcommands/report b/plugins/builder-dockerfile/subcommands/report deleted file mode 100755 index 15f0f1769..000000000 --- a/plugins/builder-dockerfile/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/builder-dockerfile/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-builder-dockerfile-report "$@" diff --git a/plugins/builder-herokuish/.gitignore b/plugins/builder-herokuish/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/builder-herokuish/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/builder-herokuish/Makefile b/plugins/builder-herokuish/Makefile new file mode 100644 index 000000000..716f94f54 --- /dev/null +++ b/plugins/builder-herokuish/Makefile @@ -0,0 +1,13 @@ +GOARCH ?= amd64 +TRIGGERS = triggers/report +BUILD = report-subcommand triggers +PLUGIN_NAME = builder-herokuish + +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/builder-herokuish/go.mod b/plugins/builder-herokuish/go.mod new file mode 100644 index 000000000..526892e6e --- /dev/null +++ b/plugins/builder-herokuish/go.mod @@ -0,0 +1,29 @@ +module github.com/dokku/dokku/plugins/builder-herokuish + +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/builder-herokuish/go.sum b/plugins/builder-herokuish/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/builder-herokuish/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/builder-herokuish/internal-functions b/plugins/builder-herokuish/internal-functions old mode 100755 new mode 100644 index a5cf4182e..395f146fd --- a/plugins/builder-herokuish/internal-functions +++ b/plugins/builder-herokuish/internal-functions @@ -4,93 +4,6 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" set -eo pipefail [[ $DOKKU_TRACE ]] && set -x -cmd-builder-herokuish-report() { - declare desc="displays a builder-herokuish report for one or more apps" - declare cmd="builder-herokuish: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-builder-herokuish-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - elif [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-builder-herokuish-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true - done - else - cmd-builder-herokuish-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - fi -} - -cmd-builder-herokuish-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=( - "--builder-herokuish-computed-allowed: $(fn-builder-herokuish-computed-allowed "$APP")" - "--builder-herokuish-global-allowed: $(fn-builder-herokuish-global-allowed)" - ) - else - verify_app_name "$APP" - flag_map=( - "--builder-herokuish-computed-allowed: $(fn-builder-herokuish-computed-allowed "$APP")" - "--builder-herokuish-global-allowed: $(fn-builder-herokuish-global-allowed)" - "--builder-herokuish-allowed: $(fn-builder-herokuish-allowed "$APP")" - ) - fi - - fn-report-validate-format "$FORMAT" "$INFO_FLAG" - - if [[ "$FORMAT" == "json" ]]; then - fn-report-emit-json flag_map "builder-herokuish" - return - fi - - if [[ -z "$INFO_FLAG" ]]; then - if [[ "$APP" == "--global" ]]; then - dokku_log_info2_quiet "global builder-herokuish information" - else - dokku_log_info2_quiet "${APP} builder-herokuish 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 -} - fn-builder-herokuish-computed-allowed() { declare APP="$1" diff --git a/plugins/builder-herokuish/report b/plugins/builder-herokuish/report deleted file mode 100755 index 785102c30..000000000 --- a/plugins/builder-herokuish/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/builder-herokuish/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-builder-herokuish-report-single "$@" diff --git a/plugins/builder-herokuish/report.go b/plugins/builder-herokuish/report.go new file mode 100644 index 000000000..b5ab0b347 --- /dev/null +++ b/plugins/builder-herokuish/report.go @@ -0,0 +1,83 @@ +package builderherokuish + +import ( + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the builder-herokuish 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 + } + } + + var flags map[string]common.ReportFunc + if appName == "--global" { + flags = map[string]common.ReportFunc{ + "--builder-herokuish-computed-allowed": reportComputedAllowed, + "--builder-herokuish-global-allowed": reportGlobalAllowed, + } + } else { + flags = map[string]common.ReportFunc{ + "--builder-herokuish-computed-allowed": reportComputedAllowed, + "--builder-herokuish-global-allowed": reportGlobalAllowed, + "--builder-herokuish-allowed": reportAllowed, + } + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "builder-herokuish", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: false, + }) +} + +func reportAllowed(appName string) string { + return common.PropertyGet("builder-herokuish", appName, "allowed") +} + +func reportGlobalAllowed(appName string) string { + return common.PropertyGet("builder-herokuish", "--global", "allowed") +} + +func reportComputedAllowed(appName string) string { + allowed := reportAllowed(appName) + if allowed == "" { + allowed = reportGlobalAllowed(appName) + } + if allowed == "" { + allowed = "true" + if hostArchitecture() != "amd64" { + allowed = "false" + } + } + + return allowed +} + +// hostArchitecture returns the dpkg architecture of the host, or an empty string +// when it cannot be determined (matching the bash `dpkg --print-architecture` check) +func hostArchitecture() string { + result, err := common.CallExecCommand(common.ExecCommandInput{ + Command: "dpkg", + Args: []string{"--print-architecture"}, + }) + if err != nil { + return "" + } + + return result.StdoutContents() +} diff --git a/plugins/builder-herokuish/src/subcommands/subcommands.go b/plugins/builder-herokuish/src/subcommands/subcommands.go new file mode 100644 index 000000000..9fb446a78 --- /dev/null +++ b/plugins/builder-herokuish/src/subcommands/subcommands.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + builderherokuish "github.com/dokku/dokku/plugins/builder-herokuish" + "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("builder-herokuish:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + reportArgs, flagErr := common.ParseReportArgs("builder-herokuish", os.Args[2:]) + if flagErr == nil { + args.Parse(reportArgs.OSArgs) + appName := args.Arg(0) + if reportArgs.IsGlobal { + appName = "--global" + } + err = builderherokuish.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/builder-herokuish/src/triggers/triggers.go b/plugins/builder-herokuish/src/triggers/triggers.go new file mode 100644 index 000000000..54a6929a2 --- /dev/null +++ b/plugins/builder-herokuish/src/triggers/triggers.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + builderherokuish "github.com/dokku/dokku/plugins/builder-herokuish" + "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 = builderherokuish.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/builder-herokuish/subcommands.go b/plugins/builder-herokuish/subcommands.go new file mode 100644 index 000000000..5d6971762 --- /dev/null +++ b/plugins/builder-herokuish/subcommands.go @@ -0,0 +1,29 @@ +package builderherokuish + +import ( + "errors" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandReport displays a builder-herokuish 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/builder-herokuish/subcommands/report b/plugins/builder-herokuish/subcommands/report deleted file mode 100755 index d99bfa2fb..000000000 --- a/plugins/builder-herokuish/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/builder-herokuish/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-builder-herokuish-report "$@" diff --git a/plugins/builder-lambda/.gitignore b/plugins/builder-lambda/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/builder-lambda/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/builder-lambda/Makefile b/plugins/builder-lambda/Makefile new file mode 100644 index 000000000..7c29679a8 --- /dev/null +++ b/plugins/builder-lambda/Makefile @@ -0,0 +1,13 @@ +GOARCH ?= amd64 +TRIGGERS = triggers/report +BUILD = report-subcommand triggers +PLUGIN_NAME = builder-lambda + +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/builder-lambda/go.mod b/plugins/builder-lambda/go.mod new file mode 100644 index 000000000..6568b612a --- /dev/null +++ b/plugins/builder-lambda/go.mod @@ -0,0 +1,29 @@ +module github.com/dokku/dokku/plugins/builder-lambda + +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/builder-lambda/go.sum b/plugins/builder-lambda/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/builder-lambda/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/builder-lambda/internal-functions b/plugins/builder-lambda/internal-functions old mode 100755 new mode 100644 index 98af8d6b7..cb9069a68 --- a/plugins/builder-lambda/internal-functions +++ b/plugins/builder-lambda/internal-functions @@ -4,93 +4,6 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" set -eo pipefail [[ $DOKKU_TRACE ]] && set -x -cmd-builder-lambda-report() { - declare desc="displays a builder-lambda report for one or more apps" - declare cmd="builder-lambda: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-builder-lambda-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - elif [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-builder-lambda-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true - done - else - cmd-builder-lambda-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - fi -} - -cmd-builder-lambda-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=( - "--builder-lambda-computed-lambdayml-path: $(fn-builder-lambda-computed-lambdayml-path "$APP")" - "--builder-lambda-global-lambdayml-path: $(fn-builder-lambda-global-lambdayml-path "$APP")" - ) - else - verify_app_name "$APP" - flag_map=( - "--builder-lambda-computed-lambdayml-path: $(fn-builder-lambda-computed-lambdayml-path "$APP")" - "--builder-lambda-global-lambdayml-path: $(fn-builder-lambda-global-lambdayml-path "$APP")" - "--builder-lambda-lambdayml-path: $(fn-builder-lambda-lambdayml-path "$APP")" - ) - fi - - fn-report-validate-format "$FORMAT" "$INFO_FLAG" - - if [[ "$FORMAT" == "json" ]]; then - fn-report-emit-json flag_map "builder-lambda" - return - fi - - if [[ -z "$INFO_FLAG" ]]; then - if [[ "$APP" == "--global" ]]; then - dokku_log_info2_quiet "global builder-lambda information" - else - dokku_log_info2_quiet "${APP} builder-lambda 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 -} - fn-builder-lambda-computed-lambdayml-path() { declare APP="$1" diff --git a/plugins/builder-lambda/report b/plugins/builder-lambda/report deleted file mode 100755 index 3271b3684..000000000 --- a/plugins/builder-lambda/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/builder-lambda/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-builder-lambda-report-single "$@" diff --git a/plugins/builder-lambda/report.go b/plugins/builder-lambda/report.go new file mode 100644 index 000000000..e9290a75e --- /dev/null +++ b/plugins/builder-lambda/report.go @@ -0,0 +1,66 @@ +package builderlambda + +import ( + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the builder-lambda 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 + } + } + + var flags map[string]common.ReportFunc + if appName == "--global" { + flags = map[string]common.ReportFunc{ + "--builder-lambda-computed-lambdayml-path": reportComputedLambdaymlPath, + "--builder-lambda-global-lambdayml-path": reportGlobalLambdaymlPath, + } + } else { + flags = map[string]common.ReportFunc{ + "--builder-lambda-computed-lambdayml-path": reportComputedLambdaymlPath, + "--builder-lambda-global-lambdayml-path": reportGlobalLambdaymlPath, + "--builder-lambda-lambdayml-path": reportLambdaymlPath, + } + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "builder-lambda", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: false, + }) +} + +func reportLambdaymlPath(appName string) string { + return common.PropertyGet("builder-lambda", appName, "lambdayml-path") +} + +func reportGlobalLambdaymlPath(appName string) string { + return common.PropertyGet("builder-lambda", "--global", "lambdayml-path") +} + +func reportComputedLambdaymlPath(appName string) string { + value := reportLambdaymlPath(appName) + if value == "" { + value = reportGlobalLambdaymlPath(appName) + } + if value == "" { + value = "lambda.yml" + } + + return value +} diff --git a/plugins/builder-lambda/src/subcommands/subcommands.go b/plugins/builder-lambda/src/subcommands/subcommands.go new file mode 100644 index 000000000..9f8307e5e --- /dev/null +++ b/plugins/builder-lambda/src/subcommands/subcommands.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + builderlambda "github.com/dokku/dokku/plugins/builder-lambda" + "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("builder-lambda:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + reportArgs, flagErr := common.ParseReportArgs("builder-lambda", os.Args[2:]) + if flagErr == nil { + args.Parse(reportArgs.OSArgs) + appName := args.Arg(0) + if reportArgs.IsGlobal { + appName = "--global" + } + err = builderlambda.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/builder-lambda/src/triggers/triggers.go b/plugins/builder-lambda/src/triggers/triggers.go new file mode 100644 index 000000000..f6832b561 --- /dev/null +++ b/plugins/builder-lambda/src/triggers/triggers.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + builderlambda "github.com/dokku/dokku/plugins/builder-lambda" + "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 = builderlambda.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/builder-lambda/subcommands.go b/plugins/builder-lambda/subcommands.go new file mode 100644 index 000000000..c7634558a --- /dev/null +++ b/plugins/builder-lambda/subcommands.go @@ -0,0 +1,29 @@ +package builderlambda + +import ( + "errors" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandReport displays a builder-lambda 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/builder-lambda/subcommands/report b/plugins/builder-lambda/subcommands/report deleted file mode 100755 index 9f56cd32f..000000000 --- a/plugins/builder-lambda/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/builder-lambda/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-builder-lambda-report "$@" diff --git a/plugins/builder-nixpacks/.gitignore b/plugins/builder-nixpacks/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/builder-nixpacks/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/builder-nixpacks/Makefile b/plugins/builder-nixpacks/Makefile new file mode 100644 index 000000000..79979a35e --- /dev/null +++ b/plugins/builder-nixpacks/Makefile @@ -0,0 +1,13 @@ +GOARCH ?= amd64 +TRIGGERS = triggers/report +BUILD = report-subcommand triggers +PLUGIN_NAME = builder-nixpacks + +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/builder-nixpacks/go.mod b/plugins/builder-nixpacks/go.mod new file mode 100644 index 000000000..3470e287c --- /dev/null +++ b/plugins/builder-nixpacks/go.mod @@ -0,0 +1,29 @@ +module github.com/dokku/dokku/plugins/builder-nixpacks + +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/builder-nixpacks/go.sum b/plugins/builder-nixpacks/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/builder-nixpacks/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/builder-nixpacks/internal-functions b/plugins/builder-nixpacks/internal-functions old mode 100755 new mode 100644 index 2350bd4ea..fab38e0a7 --- a/plugins/builder-nixpacks/internal-functions +++ b/plugins/builder-nixpacks/internal-functions @@ -4,93 +4,6 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" set -eo pipefail [[ $DOKKU_TRACE ]] && set -x -cmd-builder-nixpacks-report() { - declare desc="displays a builder-nixpacks report for one or more apps" - declare cmd="builder-nixpacks: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-builder-nixpacks-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - elif [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-builder-nixpacks-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true - done - else - cmd-builder-nixpacks-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - fi -} - -cmd-builder-nixpacks-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=( - "--builder-nixpacks-computed-nixpackstoml-path: $(fn-builder-nixpacks-computed-nixpackstoml-path "$APP")" - "--builder-nixpacks-global-nixpackstoml-path: $(fn-builder-nixpacks-global-nixpackstoml-path "$APP")" - ) - else - verify_app_name "$APP" - flag_map=( - "--builder-nixpacks-computed-nixpackstoml-path: $(fn-builder-nixpacks-computed-nixpackstoml-path "$APP")" - "--builder-nixpacks-global-nixpackstoml-path: $(fn-builder-nixpacks-global-nixpackstoml-path "$APP")" - "--builder-nixpacks-nixpackstoml-path: $(fn-builder-nixpacks-nixpackstoml-path "$APP")" - ) - fi - - fn-report-validate-format "$FORMAT" "$INFO_FLAG" - - if [[ "$FORMAT" == "json" ]]; then - fn-report-emit-json flag_map "builder-nixpacks" - return - fi - - if [[ -z "$INFO_FLAG" ]]; then - if [[ "$APP" == "--global" ]]; then - dokku_log_info2_quiet "global builder-nixpacks information" - else - dokku_log_info2_quiet "${APP} builder-nixpacks 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 -} - fn-builder-nixpacks-computed-nixpackstoml-path() { declare APP="$1" diff --git a/plugins/builder-nixpacks/report b/plugins/builder-nixpacks/report deleted file mode 100755 index 727356552..000000000 --- a/plugins/builder-nixpacks/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/builder-nixpacks/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-builder-nixpacks-report-single "$@" diff --git a/plugins/builder-nixpacks/report.go b/plugins/builder-nixpacks/report.go new file mode 100644 index 000000000..ecd1322cc --- /dev/null +++ b/plugins/builder-nixpacks/report.go @@ -0,0 +1,66 @@ +package buildernixpacks + +import ( + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the builder-nixpacks 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 + } + } + + var flags map[string]common.ReportFunc + if appName == "--global" { + flags = map[string]common.ReportFunc{ + "--builder-nixpacks-computed-nixpackstoml-path": reportComputedNixpackstomlPath, + "--builder-nixpacks-global-nixpackstoml-path": reportGlobalNixpackstomlPath, + } + } else { + flags = map[string]common.ReportFunc{ + "--builder-nixpacks-computed-nixpackstoml-path": reportComputedNixpackstomlPath, + "--builder-nixpacks-global-nixpackstoml-path": reportGlobalNixpackstomlPath, + "--builder-nixpacks-nixpackstoml-path": reportNixpackstomlPath, + } + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "builder-nixpacks", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: false, + }) +} + +func reportNixpackstomlPath(appName string) string { + return common.PropertyGet("builder-nixpacks", appName, "nixpackstoml-path") +} + +func reportGlobalNixpackstomlPath(appName string) string { + return common.PropertyGet("builder-nixpacks", "--global", "nixpackstoml-path") +} + +func reportComputedNixpackstomlPath(appName string) string { + value := reportNixpackstomlPath(appName) + if value == "" { + value = reportGlobalNixpackstomlPath(appName) + } + if value == "" { + value = "nixpacks.toml" + } + + return value +} diff --git a/plugins/builder-nixpacks/src/subcommands/subcommands.go b/plugins/builder-nixpacks/src/subcommands/subcommands.go new file mode 100644 index 000000000..9016333ae --- /dev/null +++ b/plugins/builder-nixpacks/src/subcommands/subcommands.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + buildernixpacks "github.com/dokku/dokku/plugins/builder-nixpacks" + "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("builder-nixpacks:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + reportArgs, flagErr := common.ParseReportArgs("builder-nixpacks", os.Args[2:]) + if flagErr == nil { + args.Parse(reportArgs.OSArgs) + appName := args.Arg(0) + if reportArgs.IsGlobal { + appName = "--global" + } + err = buildernixpacks.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/builder-nixpacks/src/triggers/triggers.go b/plugins/builder-nixpacks/src/triggers/triggers.go new file mode 100644 index 000000000..57fafcf5e --- /dev/null +++ b/plugins/builder-nixpacks/src/triggers/triggers.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + buildernixpacks "github.com/dokku/dokku/plugins/builder-nixpacks" + "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 = buildernixpacks.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/builder-nixpacks/subcommands.go b/plugins/builder-nixpacks/subcommands.go new file mode 100644 index 000000000..ec7b91958 --- /dev/null +++ b/plugins/builder-nixpacks/subcommands.go @@ -0,0 +1,29 @@ +package buildernixpacks + +import ( + "errors" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandReport displays a builder-nixpacks 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/builder-nixpacks/subcommands/report b/plugins/builder-nixpacks/subcommands/report deleted file mode 100755 index 4ea4b1d13..000000000 --- a/plugins/builder-nixpacks/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/builder-nixpacks/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-builder-nixpacks-report "$@" diff --git a/plugins/builder-pack/.gitignore b/plugins/builder-pack/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/builder-pack/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/builder-pack/Makefile b/plugins/builder-pack/Makefile new file mode 100644 index 000000000..ab09df0e2 --- /dev/null +++ b/plugins/builder-pack/Makefile @@ -0,0 +1,13 @@ +GOARCH ?= amd64 +TRIGGERS = triggers/report +BUILD = report-subcommand triggers +PLUGIN_NAME = builder-pack + +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/builder-pack/go.mod b/plugins/builder-pack/go.mod new file mode 100644 index 000000000..411219c8a --- /dev/null +++ b/plugins/builder-pack/go.mod @@ -0,0 +1,29 @@ +module github.com/dokku/dokku/plugins/builder-pack + +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/builder-pack/go.sum b/plugins/builder-pack/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/builder-pack/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/builder-pack/internal-functions b/plugins/builder-pack/internal-functions old mode 100755 new mode 100644 index f127f77a0..f81f131ea --- a/plugins/builder-pack/internal-functions +++ b/plugins/builder-pack/internal-functions @@ -4,93 +4,6 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" set -eo pipefail [[ $DOKKU_TRACE ]] && set -x -cmd-builder-pack-report() { - declare desc="displays a builder-pack report for one or more apps" - declare cmd="builder-pack: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-builder-pack-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - elif [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-builder-pack-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true - done - else - cmd-builder-pack-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - fi -} - -cmd-builder-pack-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=( - "--builder-pack-computed-projecttoml-path: $(fn-builder-pack-computed-projecttoml-path "$APP")" - "--builder-pack-global-projecttoml-path: $(fn-builder-pack-global-projecttoml-path "$APP")" - ) - else - verify_app_name "$APP" - flag_map=( - "--builder-pack-computed-projecttoml-path: $(fn-builder-pack-computed-projecttoml-path "$APP")" - "--builder-pack-global-projecttoml-path: $(fn-builder-pack-global-projecttoml-path "$APP")" - "--builder-pack-projecttoml-path: $(fn-builder-pack-projecttoml-path "$APP")" - ) - fi - - fn-report-validate-format "$FORMAT" "$INFO_FLAG" - - if [[ "$FORMAT" == "json" ]]; then - fn-report-emit-json flag_map "builder-pack" - return - fi - - if [[ -z "$INFO_FLAG" ]]; then - if [[ "$APP" == "--global" ]]; then - dokku_log_info2_quiet "global builder-pack information" - else - dokku_log_info2_quiet "${APP} builder-pack 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 -} - fn-builder-pack-computed-projecttoml-path() { declare APP="$1" diff --git a/plugins/builder-pack/report b/plugins/builder-pack/report deleted file mode 100755 index 600703509..000000000 --- a/plugins/builder-pack/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/builder-pack/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-builder-pack-report-single "$@" diff --git a/plugins/builder-pack/report.go b/plugins/builder-pack/report.go new file mode 100644 index 000000000..c1949c457 --- /dev/null +++ b/plugins/builder-pack/report.go @@ -0,0 +1,66 @@ +package builderpack + +import ( + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the builder-pack 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 + } + } + + var flags map[string]common.ReportFunc + if appName == "--global" { + flags = map[string]common.ReportFunc{ + "--builder-pack-computed-projecttoml-path": reportComputedProjecttomlPath, + "--builder-pack-global-projecttoml-path": reportGlobalProjecttomlPath, + } + } else { + flags = map[string]common.ReportFunc{ + "--builder-pack-computed-projecttoml-path": reportComputedProjecttomlPath, + "--builder-pack-global-projecttoml-path": reportGlobalProjecttomlPath, + "--builder-pack-projecttoml-path": reportProjecttomlPath, + } + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "builder-pack", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: false, + }) +} + +func reportProjecttomlPath(appName string) string { + return common.PropertyGet("builder-pack", appName, "projecttoml-path") +} + +func reportGlobalProjecttomlPath(appName string) string { + return common.PropertyGet("builder-pack", "--global", "projecttoml-path") +} + +func reportComputedProjecttomlPath(appName string) string { + value := reportProjecttomlPath(appName) + if value == "" { + value = reportGlobalProjecttomlPath(appName) + } + if value == "" { + value = "project.toml" + } + + return value +} diff --git a/plugins/builder-pack/src/subcommands/subcommands.go b/plugins/builder-pack/src/subcommands/subcommands.go new file mode 100644 index 000000000..b78f01b91 --- /dev/null +++ b/plugins/builder-pack/src/subcommands/subcommands.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + builderpack "github.com/dokku/dokku/plugins/builder-pack" + "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("builder-pack:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + reportArgs, flagErr := common.ParseReportArgs("builder-pack", os.Args[2:]) + if flagErr == nil { + args.Parse(reportArgs.OSArgs) + appName := args.Arg(0) + if reportArgs.IsGlobal { + appName = "--global" + } + err = builderpack.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/builder-pack/src/triggers/triggers.go b/plugins/builder-pack/src/triggers/triggers.go new file mode 100644 index 000000000..79a63f32a --- /dev/null +++ b/plugins/builder-pack/src/triggers/triggers.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + builderpack "github.com/dokku/dokku/plugins/builder-pack" + "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 = builderpack.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/builder-pack/subcommands.go b/plugins/builder-pack/subcommands.go new file mode 100644 index 000000000..363cd781e --- /dev/null +++ b/plugins/builder-pack/subcommands.go @@ -0,0 +1,29 @@ +package builderpack + +import ( + "errors" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandReport displays a builder-pack 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/builder-pack/subcommands/report b/plugins/builder-pack/subcommands/report deleted file mode 100755 index 03f6be771..000000000 --- a/plugins/builder-pack/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/builder-pack/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-builder-pack-report "$@" diff --git a/plugins/builder-railpack/.gitignore b/plugins/builder-railpack/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/builder-railpack/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/builder-railpack/Makefile b/plugins/builder-railpack/Makefile new file mode 100644 index 000000000..73f557fa4 --- /dev/null +++ b/plugins/builder-railpack/Makefile @@ -0,0 +1,13 @@ +GOARCH ?= amd64 +TRIGGERS = triggers/report +BUILD = report-subcommand triggers +PLUGIN_NAME = builder-railpack + +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/builder-railpack/go.mod b/plugins/builder-railpack/go.mod new file mode 100644 index 000000000..f517a86e6 --- /dev/null +++ b/plugins/builder-railpack/go.mod @@ -0,0 +1,29 @@ +module github.com/dokku/dokku/plugins/builder-railpack + +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/builder-railpack/go.sum b/plugins/builder-railpack/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/builder-railpack/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/builder-railpack/internal-functions b/plugins/builder-railpack/internal-functions old mode 100755 new mode 100644 index 0dc979e46..8d71c35fc --- a/plugins/builder-railpack/internal-functions +++ b/plugins/builder-railpack/internal-functions @@ -4,93 +4,6 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" set -eo pipefail [[ $DOKKU_TRACE ]] && set -x -cmd-builder-railpack-report() { - declare desc="displays a builder-railpack report for one or more apps" - declare cmd="builder-railpack: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-builder-railpack-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - elif [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-builder-railpack-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true - done - else - cmd-builder-railpack-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - fi -} - -cmd-builder-railpack-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=( - "--builder-railpack-computed-railpackjson-path: $(fn-builder-railpack-computed-railpackjson-path "$APP")" - "--builder-railpack-global-railpackjson-path: $(fn-builder-railpack-global-railpackjson-path "$APP")" - ) - else - verify_app_name "$APP" - flag_map=( - "--builder-railpack-computed-railpackjson-path: $(fn-builder-railpack-computed-railpackjson-path "$APP")" - "--builder-railpack-global-railpackjson-path: $(fn-builder-railpack-global-railpackjson-path "$APP")" - "--builder-railpack-railpackjson-path: $(fn-builder-railpack-railpackjson-path "$APP")" - ) - fi - - fn-report-validate-format "$FORMAT" "$INFO_FLAG" - - if [[ "$FORMAT" == "json" ]]; then - fn-report-emit-json flag_map "builder-railpack" - return - fi - - if [[ -z "$INFO_FLAG" ]]; then - if [[ "$APP" == "--global" ]]; then - dokku_log_info2_quiet "global builder-railpack information" - else - dokku_log_info2_quiet "${APP} builder-railpack 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 -} - fn-builder-railpack-computed-railpackjson-path() { declare APP="$1" diff --git a/plugins/builder-railpack/report b/plugins/builder-railpack/report deleted file mode 100755 index 6fbd0d855..000000000 --- a/plugins/builder-railpack/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/builder-railpack/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-builder-railpack-report-single "$@" diff --git a/plugins/builder-railpack/report.go b/plugins/builder-railpack/report.go new file mode 100644 index 000000000..887bb4e65 --- /dev/null +++ b/plugins/builder-railpack/report.go @@ -0,0 +1,66 @@ +package builderrailpack + +import ( + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the builder-railpack 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 + } + } + + var flags map[string]common.ReportFunc + if appName == "--global" { + flags = map[string]common.ReportFunc{ + "--builder-railpack-computed-railpackjson-path": reportComputedRailpackjsonPath, + "--builder-railpack-global-railpackjson-path": reportGlobalRailpackjsonPath, + } + } else { + flags = map[string]common.ReportFunc{ + "--builder-railpack-computed-railpackjson-path": reportComputedRailpackjsonPath, + "--builder-railpack-global-railpackjson-path": reportGlobalRailpackjsonPath, + "--builder-railpack-railpackjson-path": reportRailpackjsonPath, + } + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "builder-railpack", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: false, + }) +} + +func reportRailpackjsonPath(appName string) string { + return common.PropertyGet("builder-railpack", appName, "railpackjson-path") +} + +func reportGlobalRailpackjsonPath(appName string) string { + return common.PropertyGet("builder-railpack", "--global", "railpackjson-path") +} + +func reportComputedRailpackjsonPath(appName string) string { + value := reportRailpackjsonPath(appName) + if value == "" { + value = reportGlobalRailpackjsonPath(appName) + } + if value == "" { + value = "railpack.json" + } + + return value +} diff --git a/plugins/builder-railpack/src/subcommands/subcommands.go b/plugins/builder-railpack/src/subcommands/subcommands.go new file mode 100644 index 000000000..12a610b0f --- /dev/null +++ b/plugins/builder-railpack/src/subcommands/subcommands.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + builderrailpack "github.com/dokku/dokku/plugins/builder-railpack" + "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("builder-railpack:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + reportArgs, flagErr := common.ParseReportArgs("builder-railpack", os.Args[2:]) + if flagErr == nil { + args.Parse(reportArgs.OSArgs) + appName := args.Arg(0) + if reportArgs.IsGlobal { + appName = "--global" + } + err = builderrailpack.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/builder-railpack/src/triggers/triggers.go b/plugins/builder-railpack/src/triggers/triggers.go new file mode 100644 index 000000000..9e6327592 --- /dev/null +++ b/plugins/builder-railpack/src/triggers/triggers.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + builderrailpack "github.com/dokku/dokku/plugins/builder-railpack" + "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 = builderrailpack.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/builder-railpack/subcommands.go b/plugins/builder-railpack/subcommands.go new file mode 100644 index 000000000..f8ac38c3e --- /dev/null +++ b/plugins/builder-railpack/subcommands.go @@ -0,0 +1,29 @@ +package builderrailpack + +import ( + "errors" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandReport displays a builder-railpack 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/builder-railpack/subcommands/report b/plugins/builder-railpack/subcommands/report deleted file mode 100755 index 9801d5f29..000000000 --- a/plugins/builder-railpack/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/builder-railpack/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-builder-railpack-report "$@" From 5faabea3d405844e5f34e9febd04cbce5f5f48a2 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 7 Jul 2026 06:29:01 -0400 Subject: [PATCH 2/9] feat: port vhost proxy :report subcommands to golang The bash :report implementations for the caddy, haproxy, openresty, and traefik proxy plugins are replaced with a compiled golang binary that collects report keys in parallel and marshals json directly. The traefik dns-provider values keep their masking behaviour, remaining hidden in the default stdout report while surfacing for --format json or an explicit flag query. These four plugins previously had no unit tests, so a bats suite covering the report matrix is added for each. --- go.work | 4 + plugins/caddy-vhosts/.gitignore | 4 + plugins/caddy-vhosts/Makefile | 13 + plugins/caddy-vhosts/command-functions | 107 ------- plugins/caddy-vhosts/go.mod | 29 ++ plugins/caddy-vhosts/go.sum | 55 ++++ plugins/caddy-vhosts/report | 6 - plugins/caddy-vhosts/report.go | 155 ++++++++++ .../src/subcommands/subcommands.go | 40 +++ plugins/caddy-vhosts/src/triggers/triggers.go | 31 ++ plugins/caddy-vhosts/subcommands.go | 29 ++ plugins/caddy-vhosts/subcommands/report | 6 - plugins/haproxy-vhosts/.gitignore | 4 + plugins/haproxy-vhosts/Makefile | 13 + plugins/haproxy-vhosts/command-functions | 88 ------ plugins/haproxy-vhosts/go.mod | 29 ++ plugins/haproxy-vhosts/go.sum | 55 ++++ plugins/haproxy-vhosts/report | 6 - plugins/haproxy-vhosts/report.go | 130 +++++++++ .../src/subcommands/subcommands.go | 40 +++ .../haproxy-vhosts/src/triggers/triggers.go | 31 ++ plugins/haproxy-vhosts/subcommands.go | 29 ++ plugins/haproxy-vhosts/subcommands/report | 6 - plugins/openresty-vhosts/.gitignore | 4 + plugins/openresty-vhosts/Makefile | 13 + plugins/openresty-vhosts/command-functions | 237 --------------- plugins/openresty-vhosts/go.mod | 29 ++ plugins/openresty-vhosts/go.sum | 55 ++++ plugins/openresty-vhosts/report | 6 - plugins/openresty-vhosts/report.go | 173 +++++++++++ .../src/subcommands/subcommands.go | 40 +++ .../openresty-vhosts/src/triggers/triggers.go | 31 ++ plugins/openresty-vhosts/subcommands.go | 29 ++ plugins/openresty-vhosts/subcommands/report | 6 - plugins/traefik-vhosts/.gitignore | 4 + plugins/traefik-vhosts/Makefile | 13 + plugins/traefik-vhosts/command-functions | 125 -------- plugins/traefik-vhosts/go.mod | 29 ++ plugins/traefik-vhosts/go.sum | 55 ++++ plugins/traefik-vhosts/report | 6 - plugins/traefik-vhosts/report.go | 271 ++++++++++++++++++ .../src/subcommands/subcommands.go | 40 +++ .../traefik-vhosts/src/triggers/triggers.go | 31 ++ plugins/traefik-vhosts/subcommands.go | 29 ++ plugins/traefik-vhosts/subcommands/report | 6 - tests/unit/caddy-vhosts.bats | 84 ++++++ tests/unit/haproxy-vhosts.bats | 76 +++++ tests/unit/openresty-vhosts.bats | 84 ++++++ tests/unit/traefik-vhosts.bats | 72 +++++ 49 files changed, 1853 insertions(+), 605 deletions(-) create mode 100644 plugins/caddy-vhosts/.gitignore create mode 100644 plugins/caddy-vhosts/Makefile mode change 100755 => 100644 plugins/caddy-vhosts/command-functions create mode 100644 plugins/caddy-vhosts/go.mod create mode 100644 plugins/caddy-vhosts/go.sum delete mode 100755 plugins/caddy-vhosts/report create mode 100644 plugins/caddy-vhosts/report.go create mode 100644 plugins/caddy-vhosts/src/subcommands/subcommands.go create mode 100644 plugins/caddy-vhosts/src/triggers/triggers.go create mode 100644 plugins/caddy-vhosts/subcommands.go delete mode 100755 plugins/caddy-vhosts/subcommands/report create mode 100644 plugins/haproxy-vhosts/.gitignore create mode 100644 plugins/haproxy-vhosts/Makefile mode change 100755 => 100644 plugins/haproxy-vhosts/command-functions create mode 100644 plugins/haproxy-vhosts/go.mod create mode 100644 plugins/haproxy-vhosts/go.sum delete mode 100755 plugins/haproxy-vhosts/report create mode 100644 plugins/haproxy-vhosts/report.go create mode 100644 plugins/haproxy-vhosts/src/subcommands/subcommands.go create mode 100644 plugins/haproxy-vhosts/src/triggers/triggers.go create mode 100644 plugins/haproxy-vhosts/subcommands.go delete mode 100755 plugins/haproxy-vhosts/subcommands/report create mode 100644 plugins/openresty-vhosts/.gitignore create mode 100644 plugins/openresty-vhosts/Makefile mode change 100755 => 100644 plugins/openresty-vhosts/command-functions create mode 100644 plugins/openresty-vhosts/go.mod create mode 100644 plugins/openresty-vhosts/go.sum delete mode 100755 plugins/openresty-vhosts/report create mode 100644 plugins/openresty-vhosts/report.go create mode 100644 plugins/openresty-vhosts/src/subcommands/subcommands.go create mode 100644 plugins/openresty-vhosts/src/triggers/triggers.go create mode 100644 plugins/openresty-vhosts/subcommands.go delete mode 100755 plugins/openresty-vhosts/subcommands/report create mode 100644 plugins/traefik-vhosts/.gitignore create mode 100644 plugins/traefik-vhosts/Makefile mode change 100755 => 100644 plugins/traefik-vhosts/command-functions create mode 100644 plugins/traefik-vhosts/go.mod create mode 100644 plugins/traefik-vhosts/go.sum delete mode 100755 plugins/traefik-vhosts/report create mode 100644 plugins/traefik-vhosts/report.go create mode 100644 plugins/traefik-vhosts/src/subcommands/subcommands.go create mode 100644 plugins/traefik-vhosts/src/triggers/triggers.go create mode 100644 plugins/traefik-vhosts/subcommands.go delete mode 100755 plugins/traefik-vhosts/subcommands/report create mode 100644 tests/unit/caddy-vhosts.bats create mode 100644 tests/unit/haproxy-vhosts.bats create mode 100644 tests/unit/openresty-vhosts.bats create mode 100644 tests/unit/traefik-vhosts.bats 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" +} From 8d0c36bad6d099e4efca5ab5a5123dd47cdcaaa8 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 7 Jul 2026 06:43:31 -0400 Subject: [PATCH 3/9] feat: port checks and domains :report subcommands to golang The bash :report implementations for the checks and domains plugins are replaced with a compiled golang binary that collects report keys in parallel and marshals json directly. The domains global report header now matches the shared renderer used by every other golang report, and its bats assertion is updated accordingly. --- go.work | 2 + plugins/checks/.gitignore | 4 + plugins/checks/Makefile | 13 ++ plugins/checks/go.mod | 29 ++++ plugins/checks/go.sum | 55 ++++++++ plugins/checks/internal-functions | 89 ------------- plugins/checks/report | 6 - plugins/checks/report.go | 86 ++++++++++++ plugins/checks/src/subcommands/subcommands.go | 40 ++++++ plugins/checks/src/triggers/triggers.go | 31 +++++ plugins/checks/subcommands.go | 29 ++++ plugins/checks/subcommands/report | 6 - plugins/domains/.gitignore | 4 + plugins/domains/Makefile | 13 ++ plugins/domains/go.mod | 29 ++++ plugins/domains/go.sum | 55 ++++++++ plugins/domains/internal-functions | 89 ------------- plugins/domains/report | 6 - plugins/domains/report.go | 124 ++++++++++++++++++ .../domains/src/subcommands/subcommands.go | 40 ++++++ plugins/domains/src/triggers/triggers.go | 31 +++++ plugins/domains/subcommands.go | 29 ++++ plugins/domains/subcommands/report | 6 - tests/unit/domains.bats | 8 +- 24 files changed, 621 insertions(+), 203 deletions(-) create mode 100644 plugins/checks/.gitignore create mode 100644 plugins/checks/Makefile create mode 100644 plugins/checks/go.mod create mode 100644 plugins/checks/go.sum mode change 100755 => 100644 plugins/checks/internal-functions delete mode 100755 plugins/checks/report create mode 100644 plugins/checks/report.go create mode 100644 plugins/checks/src/subcommands/subcommands.go create mode 100644 plugins/checks/src/triggers/triggers.go create mode 100644 plugins/checks/subcommands.go delete mode 100755 plugins/checks/subcommands/report create mode 100644 plugins/domains/.gitignore create mode 100644 plugins/domains/Makefile create mode 100644 plugins/domains/go.mod create mode 100644 plugins/domains/go.sum mode change 100755 => 100644 plugins/domains/internal-functions delete mode 100755 plugins/domains/report create mode 100644 plugins/domains/report.go create mode 100644 plugins/domains/src/subcommands/subcommands.go create mode 100644 plugins/domains/src/triggers/triggers.go create mode 100644 plugins/domains/subcommands.go delete mode 100755 plugins/domains/subcommands/report diff --git a/go.work b/go.work index faac91ae3..afe21daad 100644 --- a/go.work +++ b/go.work @@ -12,10 +12,12 @@ use ( ./plugins/builder-railpack ./plugins/buildpacks ./plugins/caddy-vhosts + ./plugins/checks ./plugins/common ./plugins/config ./plugins/cron ./plugins/docker-options + ./plugins/domains ./plugins/haproxy-vhosts ./plugins/logs ./plugins/network diff --git a/plugins/checks/.gitignore b/plugins/checks/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/checks/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/checks/Makefile b/plugins/checks/Makefile new file mode 100644 index 000000000..0cfaa38e7 --- /dev/null +++ b/plugins/checks/Makefile @@ -0,0 +1,13 @@ +GOARCH ?= amd64 +TRIGGERS = triggers/report +BUILD = report-subcommand triggers +PLUGIN_NAME = checks + +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/checks/go.mod b/plugins/checks/go.mod new file mode 100644 index 000000000..998259655 --- /dev/null +++ b/plugins/checks/go.mod @@ -0,0 +1,29 @@ +module github.com/dokku/dokku/plugins/checks + +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/checks/go.sum b/plugins/checks/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/checks/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/checks/internal-functions b/plugins/checks/internal-functions old mode 100755 new mode 100644 index 9e4dacd84..7ab635962 --- a/plugins/checks/internal-functions +++ b/plugins/checks/internal-functions @@ -5,95 +5,6 @@ source "$PLUGIN_AVAILABLE_PATH/checks/functions" set -eo pipefail [[ $DOKKU_TRACE ]] && set -x -cmd-checks-report() { - declare desc="shows reports for an app" - declare cmd="checks: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-checks-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - elif [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-checks-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true - done - else - cmd-checks-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - fi -} - -cmd-checks-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=( - "--checks-computed-wait-to-retire: $(fn-checks-computed-wait-to-retire "$APP")" - "--checks-global-wait-to-retire: $(fn-checks-global-wait-to-retire "$APP")" - ) - else - verify_app_name "$APP" - flag_map=( - "--checks-disabled-list: $(fn-checks-disabled-list "$APP")" - "--checks-skipped-list: $(fn-checks-skipped-list "$APP")" - "--checks-computed-wait-to-retire: $(fn-checks-computed-wait-to-retire "$APP")" - "--checks-global-wait-to-retire: $(fn-checks-global-wait-to-retire "$APP")" - "--checks-wait-to-retire: $(fn-checks-wait-to-retire "$APP")" - ) - fi - - fn-report-validate-format "$FORMAT" "$INFO_FLAG" - - if [[ "$FORMAT" == "json" ]]; then - fn-report-emit-json flag_map "checks" - return - fi - - if [[ -z "$INFO_FLAG" ]]; then - if [[ "$APP" == "--global" ]]; then - dokku_log_info2_quiet "global checks information" - else - dokku_log_info2_quiet "$APP checks 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 -} - fn-checks-disabled-list() { declare APP="$1" diff --git a/plugins/checks/report b/plugins/checks/report deleted file mode 100755 index 558804912..000000000 --- a/plugins/checks/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/checks/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-checks-report-single "$@" diff --git a/plugins/checks/report.go b/plugins/checks/report.go new file mode 100644 index 000000000..e93f48a78 --- /dev/null +++ b/plugins/checks/report.go @@ -0,0 +1,86 @@ +package checks + +import ( + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the checks 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 + } + } + + var flags map[string]common.ReportFunc + if appName == "--global" { + flags = map[string]common.ReportFunc{ + "--checks-computed-wait-to-retire": reportComputedWaitToRetire, + "--checks-global-wait-to-retire": reportGlobalWaitToRetire, + } + } else { + flags = map[string]common.ReportFunc{ + "--checks-disabled-list": reportDisabledList, + "--checks-skipped-list": reportSkippedList, + "--checks-computed-wait-to-retire": reportComputedWaitToRetire, + "--checks-global-wait-to-retire": reportGlobalWaitToRetire, + "--checks-wait-to-retire": reportWaitToRetire, + } + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "checks", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: false, + }) +} + +func reportDisabledList(appName string) string { + value := common.PropertyGet("checks", appName, "disabled") + if value == "" { + value = "none" + } + + return value +} + +func reportSkippedList(appName string) string { + value := common.PropertyGet("checks", appName, "skipped") + if value == "" { + value = "none" + } + + return value +} + +func reportWaitToRetire(appName string) string { + return common.PropertyGet("checks", appName, "wait-to-retire") +} + +func reportGlobalWaitToRetire(appName string) string { + return common.PropertyGet("checks", "--global", "wait-to-retire") +} + +func reportComputedWaitToRetire(appName string) string { + value := reportWaitToRetire(appName) + if value == "" { + value = reportGlobalWaitToRetire(appName) + } + if value == "" { + value = "60" + } + + return value +} diff --git a/plugins/checks/src/subcommands/subcommands.go b/plugins/checks/src/subcommands/subcommands.go new file mode 100644 index 000000000..fdecaa599 --- /dev/null +++ b/plugins/checks/src/subcommands/subcommands.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + checks "github.com/dokku/dokku/plugins/checks" + "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("checks:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + reportArgs, flagErr := common.ParseReportArgs("checks", os.Args[2:]) + if flagErr == nil { + args.Parse(reportArgs.OSArgs) + appName := args.Arg(0) + if reportArgs.IsGlobal { + appName = "--global" + } + err = checks.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/checks/src/triggers/triggers.go b/plugins/checks/src/triggers/triggers.go new file mode 100644 index 000000000..29732db72 --- /dev/null +++ b/plugins/checks/src/triggers/triggers.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + checks "github.com/dokku/dokku/plugins/checks" + "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 = checks.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/checks/subcommands.go b/plugins/checks/subcommands.go new file mode 100644 index 000000000..22554a536 --- /dev/null +++ b/plugins/checks/subcommands.go @@ -0,0 +1,29 @@ +package checks + +import ( + "errors" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandReport displays a checks 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/checks/subcommands/report b/plugins/checks/subcommands/report deleted file mode 100755 index b0006b8bb..000000000 --- a/plugins/checks/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/checks/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-checks-report "$@" diff --git a/plugins/domains/.gitignore b/plugins/domains/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/domains/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/domains/Makefile b/plugins/domains/Makefile new file mode 100644 index 000000000..c4698f30c --- /dev/null +++ b/plugins/domains/Makefile @@ -0,0 +1,13 @@ +GOARCH ?= amd64 +TRIGGERS = triggers/report +BUILD = report-subcommand triggers +PLUGIN_NAME = domains + +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/domains/go.mod b/plugins/domains/go.mod new file mode 100644 index 000000000..42fd7dd0f --- /dev/null +++ b/plugins/domains/go.mod @@ -0,0 +1,29 @@ +module github.com/dokku/dokku/plugins/domains + +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/domains/go.sum b/plugins/domains/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/domains/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/domains/internal-functions b/plugins/domains/internal-functions old mode 100755 new mode 100644 index 7699d5888..04f68bef6 --- a/plugins/domains/internal-functions +++ b/plugins/domains/internal-functions @@ -4,95 +4,6 @@ source "$PLUGIN_AVAILABLE_PATH/domains/functions" set -eo pipefail [[ $DOKKU_TRACE ]] && set -x -cmd-domains-report() { - declare desc="displays a domains report for one or more apps" - declare cmd="domains:report" - [[ "$1" == "$cmd" ]] && shift 1 - fn-report-parse-args "$@" - set -- "${REPORT_ARGS[@]}" - declare APP="${1:-}" INFO_FLAG="${2:-}" - - if [[ -n "$APP" ]] && [[ "$APP" != "--global" ]] && [[ "$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-domains-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - elif [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-domains-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true - done - else - cmd-domains-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - fi -} - -cmd-domains-report-single() { - declare APP="$1" INFO_FLAG="$2" FORMAT="${3:-stdout}" - if [[ "$INFO_FLAG" == "true" ]]; then - INFO_FLAG="" - fi - local flag_map=() app_flags=() global_flags=() - - if [[ "$APP" != "--global" ]]; then - verify_app_name "$APP" - app_flags=( - "--domains-app-enabled: $(fn-domains-app-enabled "$APP")" - "--domains-app-vhosts: $(fn-domains-app-vhosts "$APP" | awk '{$1=$1};1')" - ) - fi - - global_flags=( - "--domains-global-enabled: $(fn-domains-global-enabled)" - "--domains-global-vhosts: $(fn-domains-global-vhosts | awk '{$1=$1};1')" - ) - - flag_map=("${app_flags[@]}" "${global_flags[@]}") - - fn-report-validate-format "$FORMAT" "$INFO_FLAG" - - if [[ "$FORMAT" == "json" ]]; then - fn-report-emit-json flag_map "domains" - return - fi - - if [[ -z "$INFO_FLAG" ]]; then - if [[ "$APP" == "--global" ]]; then - dokku_log_info2_quiet "Global domains information" - else - dokku_log_info2_quiet "$APP domains 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 -} - fn-domains-app-enabled() { declare APP="$1" local DOMAINS_APP_ENABLED=false diff --git a/plugins/domains/report b/plugins/domains/report deleted file mode 100755 index dde95bcd7..000000000 --- a/plugins/domains/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/domains/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-domains-report-single "$@" diff --git a/plugins/domains/report.go b/plugins/domains/report.go new file mode 100644 index 000000000..cf5788393 --- /dev/null +++ b/plugins/domains/report.go @@ -0,0 +1,124 @@ +package domains + +import ( + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +var hostnameRegex = regexp.MustCompile(`^[a-z0-9.*-]*[a-z0-9*-]$`) + +// ReportSingleApp is an internal function that displays the domains 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 + } + } + + var flags map[string]common.ReportFunc + if appName == "--global" { + flags = map[string]common.ReportFunc{ + "--domains-global-enabled": reportGlobalEnabled, + "--domains-global-vhosts": reportGlobalVhosts, + } + } else { + flags = map[string]common.ReportFunc{ + "--domains-app-enabled": reportAppEnabled, + "--domains-app-vhosts": reportAppVhosts, + "--domains-global-enabled": reportGlobalEnabled, + "--domains-global-vhosts": reportGlobalVhosts, + } + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "domains", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: false, + }) +} + +func reportAppEnabled(appName string) string { + results, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{ + Trigger: "config-get", + Args: []string{appName, "NO_VHOST"}, + }) + if results.StdoutContents() == "1" { + return "false" + } + + return "true" +} + +func reportAppVhosts(appName string) string { + dokkuRoot := common.MustGetEnv("DOKKU_ROOT") + return normalizeWhitespace(readFileString(filepath.Join(dokkuRoot, appName, "VHOST"))) +} + +func reportGlobalEnabled(appName string) string { + if isGlobalVhostEnabled() { + return "true" + } + + return "false" +} + +func reportGlobalVhosts(appName string) string { + if !isGlobalVhostEnabled() { + return "" + } + + return normalizeWhitespace(globalVhostContents()) +} + +func globalVhostContents() string { + dokkuRoot := common.MustGetEnv("DOKKU_ROOT") + return readFileString(filepath.Join(dokkuRoot, "VHOST")) +} + +func isGlobalVhostEnabled() bool { + for _, line := range strings.Split(globalVhostContents(), "\n") { + if isValidHostname(line) { + return true + } + } + + return false +} + +func isValidHostname(hostname string) bool { + hostname = strings.ToLower(hostname) + if hostname == "_" { + return true + } + + return hostnameRegex.MatchString(hostname) +} + +func readFileString(path string) string { + data, err := os.ReadFile(path) + if err != nil { + return "" + } + + return string(data) +} + +func normalizeWhitespace(value string) string { + return strings.Join(strings.Fields(value), " ") +} diff --git a/plugins/domains/src/subcommands/subcommands.go b/plugins/domains/src/subcommands/subcommands.go new file mode 100644 index 000000000..71eb086d5 --- /dev/null +++ b/plugins/domains/src/subcommands/subcommands.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + domains "github.com/dokku/dokku/plugins/domains" + + 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("domains:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + reportArgs, flagErr := common.ParseReportArgs("domains", os.Args[2:]) + if flagErr == nil { + args.Parse(reportArgs.OSArgs) + appName := args.Arg(0) + if reportArgs.IsGlobal { + appName = "--global" + } + err = domains.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/domains/src/triggers/triggers.go b/plugins/domains/src/triggers/triggers.go new file mode 100644 index 000000000..62889aa28 --- /dev/null +++ b/plugins/domains/src/triggers/triggers.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + domains "github.com/dokku/dokku/plugins/domains" +) + +// 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 = domains.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/domains/subcommands.go b/plugins/domains/subcommands.go new file mode 100644 index 000000000..e20ac6352 --- /dev/null +++ b/plugins/domains/subcommands.go @@ -0,0 +1,29 @@ +package domains + +import ( + "errors" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandReport displays a domains 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/domains/subcommands/report b/plugins/domains/subcommands/report deleted file mode 100755 index cb6efbb7d..000000000 --- a/plugins/domains/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/domains/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-domains-report "$@" diff --git a/tests/unit/domains.bats b/tests/unit/domains.bats index f46882c2d..005973044 100644 --- a/tests/unit/domains.bats +++ b/tests/unit/domains.bats @@ -44,13 +44,19 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains "Global domains information" + assert_output_contains "global domains information" run /bin/bash -c "dokku domains:report --global --domains-global-enabled" echo "output: $output" echo "status: $status" assert_success assert_output "true" + + run /bin/bash -c "dokku domains:report --global --domains-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" } @test "(domains) domains" { From 6e5afb84a99e2b451f5f0f05ecf901277ab827a7 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 7 Jul 2026 06:56:51 -0400 Subject: [PATCH 4/9] feat: port certs :report subcommand to golang The bash :report implementation for the certs plugin is replaced with a compiled golang binary. The certificate inspection getters run openssl and reproduce the existing field extraction, so the ssl report keys are unchanged, while collection now happens in parallel and json is marshalled directly. The now-unused fn-ssl-* display helpers are dropped, and fn-certs-set and fn-certs-remove remain for certs:add and certs:remove. --- go.work | 1 + plugins/certs/.gitignore | 4 + plugins/certs/Makefile | 13 ++ plugins/certs/go.mod | 29 +++ plugins/certs/go.sum | 55 +++++ plugins/certs/internal-functions | 162 ------------- plugins/certs/report | 6 - plugins/certs/report.go | 233 +++++++++++++++++++ plugins/certs/src/subcommands/subcommands.go | 40 ++++ plugins/certs/src/triggers/triggers.go | 31 +++ plugins/certs/subcommands.go | 29 +++ plugins/certs/subcommands/report | 6 - 12 files changed, 435 insertions(+), 174 deletions(-) create mode 100644 plugins/certs/.gitignore create mode 100644 plugins/certs/Makefile create mode 100644 plugins/certs/go.mod create mode 100644 plugins/certs/go.sum mode change 100755 => 100644 plugins/certs/internal-functions delete mode 100755 plugins/certs/report create mode 100644 plugins/certs/report.go create mode 100644 plugins/certs/src/subcommands/subcommands.go create mode 100644 plugins/certs/src/triggers/triggers.go create mode 100644 plugins/certs/subcommands.go delete mode 100755 plugins/certs/subcommands/report diff --git a/go.work b/go.work index afe21daad..2aa88dabc 100644 --- a/go.work +++ b/go.work @@ -12,6 +12,7 @@ use ( ./plugins/builder-railpack ./plugins/buildpacks ./plugins/caddy-vhosts + ./plugins/certs ./plugins/checks ./plugins/common ./plugins/config diff --git a/plugins/certs/.gitignore b/plugins/certs/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/certs/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/certs/Makefile b/plugins/certs/Makefile new file mode 100644 index 000000000..92ef9be66 --- /dev/null +++ b/plugins/certs/Makefile @@ -0,0 +1,13 @@ +GOARCH ?= amd64 +TRIGGERS = triggers/report +BUILD = report-subcommand triggers +PLUGIN_NAME = certs + +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/certs/go.mod b/plugins/certs/go.mod new file mode 100644 index 000000000..28a0fda20 --- /dev/null +++ b/plugins/certs/go.mod @@ -0,0 +1,29 @@ +module github.com/dokku/dokku/plugins/certs + +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/certs/go.sum b/plugins/certs/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/certs/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/certs/internal-functions b/plugins/certs/internal-functions old mode 100755 new mode 100644 index 2686c0a0c..be7f27578 --- a/plugins/certs/internal-functions +++ b/plugins/certs/internal-functions @@ -46,165 +46,3 @@ fn-certs-remove() { plugn trigger post-domains-update "$APP" } -cmd-certs-report() { - declare desc="displays an ssl report for one or more apps" - declare cmd="certs: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-certs-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - elif [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-certs-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true - done - else - cmd-certs-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - fi -} - -cmd-certs-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 - verify_app_name "$APP" - flag_map=( - "--ssl-dir: $DOKKU_ROOT/$APP/tls" - "--ssl-enabled: $(fn-ssl-enabled "$APP")" - "--ssl-hostnames: $(fn-ssl-hostnames "$APP")" - "--ssl-expires-at: $(fn-ssl-expires-at "$APP")" - "--ssl-issuer: $(fn-ssl-issuer "$APP")" - "--ssl-starts-at: $(fn-ssl-starts-at "$APP")" - "--ssl-subject: $(fn-ssl-subject "$APP")" - "--ssl-verified: $(fn-ssl-verified "$APP")" - ) - fi - - fn-report-validate-format "$FORMAT" "$INFO_FLAG" - - if [[ "$FORMAT" == "json" ]]; then - fn-report-emit-json flag_map "ssl" - return - fi - - if [[ -z "$INFO_FLAG" ]]; then - if [[ "$APP" == "--global" ]]; then - dokku_log_info2_quiet "global ssl information" - else - dokku_log_info2_quiet "$APP ssl 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 -} - -fn-ssl-enabled() { - declare APP="$1" - local SSL_ENABLED=false - - if is_ssl_enabled "$APP"; then - SSL_ENABLED=true - fi - echo "$SSL_ENABLED" -} - -fn-ssl-expires-at() { - declare APP="$1" - local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" - - if is_ssl_enabled "$APP"; then - openssl x509 -in "$APP_SSL_PATH/server.crt" -noout -text | grep "Not After :" | awk -F " : " '{ print $2 }' - fi -} - -fn-ssl-hostnames() { - declare APP="$1" - - if is_ssl_enabled "$APP"; then - get_ssl_hostnames "$APP" | xargs - fi -} - -fn-ssl-issuer() { - declare APP="$1" - local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" - - if is_ssl_enabled "$APP"; then - openssl x509 -in "$APP_SSL_PATH/server.crt" -noout -text | grep "Issuer:" | head -n1 | sed -e 's/Issuer: //g' -e 's/^[[:space:]]*//' - fi -} - -fn-ssl-starts-at() { - declare APP="$1" - local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" - - if is_ssl_enabled "$APP"; then - openssl x509 -in "$APP_SSL_PATH/server.crt" -noout -text | grep "Not Before:" | awk -F ": " '{ print $2 }' - fi -} - -fn-ssl-subject() { - declare APP="$1" - local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" - - if is_ssl_enabled "$APP"; then - openssl x509 -in "$APP_SSL_PATH/server.crt" -noout -subject | sed -e "s:subject= ::g" | sed -e "s:^/::g" | sed -e "s:/:; :g" - fi -} - -fn-ssl-verified() { - declare APP="$1" - local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" - local SSL_VERIFY_OUTPUT=false SSL_SELF_SIGNED="self signed" - - if ! is_ssl_enabled "$APP"; then - return - fi - - if [[ -f "$APP_SSL_PATH/server.letsencrypt.crt" ]]; then - SSL_VERIFY_OUTPUT="$(openssl verify -verbose -purpose sslserver -CAfile "$APP_SSL_PATH/server.crt" "$APP_SSL_PATH/server.letsencrypt.crt" 2>/dev/null | awk -F ':' '{ print $2 }' | tail -1 | xargs || true)" - else - SSL_VERIFY_OUTPUT="$(openssl verify -verbose -purpose sslserver "$APP_SSL_PATH/server.crt" 2>/dev/null | awk -F ':' '{ print $2 }' | tail -1 | xargs || true)" - fi - - if [[ "$SSL_VERIFY_OUTPUT" == "OK" ]]; then - SSL_SELF_SIGNED="verified by a certificate authority" - fi - - echo "$SSL_SELF_SIGNED" -} diff --git a/plugins/certs/report b/plugins/certs/report deleted file mode 100755 index b323feb51..000000000 --- a/plugins/certs/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/certs/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-certs-report-single "$@" diff --git a/plugins/certs/report.go b/plugins/certs/report.go new file mode 100644 index 000000000..02ee5ec15 --- /dev/null +++ b/plugins/certs/report.go @@ -0,0 +1,233 @@ +package certs + +import ( + "os" + "path/filepath" + "regexp" + "sort" + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +var dnsPrefixRegex = regexp.MustCompile(`[[:space:]]*DNS:`) + +// ReportSingleApp is an internal function that displays the ssl report for one or more apps +func ReportSingleApp(appName string, format string, infoFlag string) error { + flags := map[string]common.ReportFunc{} + if appName != "--global" { + if err := common.VerifyAppName(appName); err != nil { + return err + } + flags = map[string]common.ReportFunc{ + "--ssl-dir": reportSSLDir, + "--ssl-enabled": reportSSLEnabled, + "--ssl-hostnames": reportSSLHostnames, + "--ssl-expires-at": reportSSLExpiresAt, + "--ssl-issuer": reportSSLIssuer, + "--ssl-starts-at": reportSSLStartsAt, + "--ssl-subject": reportSSLSubject, + "--ssl-verified": reportSSLVerified, + } + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "ssl", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: false, + }) +} + +func certTLSPath(appName string) string { + return filepath.Join(common.MustGetEnv("DOKKU_ROOT"), appName, "tls") +} + +func isSSLEnabled(appName string) bool { + tlsPath := certTLSPath(appName) + if _, err := os.Stat(filepath.Join(tlsPath, "server.crt")); err != nil { + return false + } + if _, err := os.Stat(filepath.Join(tlsPath, "server.key")); err != nil { + return false + } + + return true +} + +func opensslCertText(appName string) string { + result, err := common.CallExecCommand(common.ExecCommandInput{ + Command: "openssl", + Args: []string{"x509", "-in", filepath.Join(certTLSPath(appName), "server.crt"), "-noout", "-text"}, + }) + if err != nil { + return "" + } + + return result.Stdout +} + +func reportSSLDir(appName string) string { + return certTLSPath(appName) +} + +func reportSSLEnabled(appName string) string { + if isSSLEnabled(appName) { + return "true" + } + + return "false" +} + +func reportSSLExpiresAt(appName string) string { + if !isSSLEnabled(appName) { + return "" + } + + for _, line := range strings.Split(opensslCertText(appName), "\n") { + if strings.Contains(line, "Not After :") { + parts := strings.Split(line, " : ") + if len(parts) >= 2 { + return parts[1] + } + } + } + + return "" +} + +func reportSSLStartsAt(appName string) string { + if !isSSLEnabled(appName) { + return "" + } + + for _, line := range strings.Split(opensslCertText(appName), "\n") { + if strings.Contains(line, "Not Before:") { + parts := strings.Split(line, ": ") + if len(parts) >= 2 { + return parts[1] + } + } + } + + return "" +} + +func reportSSLIssuer(appName string) string { + if !isSSLEnabled(appName) { + return "" + } + + for _, line := range strings.Split(opensslCertText(appName), "\n") { + if strings.Contains(line, "Issuer:") { + line = strings.Replace(line, "Issuer: ", "", 1) + return strings.TrimLeft(line, " \t") + } + } + + return "" +} + +func reportSSLSubject(appName string) string { + if !isSSLEnabled(appName) { + return "" + } + + result, err := common.CallExecCommand(common.ExecCommandInput{ + Command: "openssl", + Args: []string{"x509", "-in", filepath.Join(certTLSPath(appName), "server.crt"), "-noout", "-subject"}, + }) + if err != nil { + return "" + } + + subject := strings.Replace(result.StdoutContents(), "subject= ", "", 1) + subject = strings.TrimPrefix(subject, "/") + return strings.ReplaceAll(subject, "/", "; ") +} + +func reportSSLVerified(appName string) string { + if !isSSLEnabled(appName) { + return "" + } + + tlsPath := certTLSPath(appName) + args := []string{"verify", "-verbose", "-purpose", "sslserver"} + if _, err := os.Stat(filepath.Join(tlsPath, "server.letsencrypt.crt")); err == nil { + args = append(args, "-CAfile", filepath.Join(tlsPath, "server.crt"), filepath.Join(tlsPath, "server.letsencrypt.crt")) + } else { + args = append(args, filepath.Join(tlsPath, "server.crt")) + } + + result, _ := common.CallExecCommand(common.ExecCommandInput{ + Command: "openssl", + Args: args, + }) + + verifyOutput := "" + if out := strings.TrimRight(result.Stdout, "\n"); out != "" { + lines := strings.Split(out, "\n") + parts := strings.Split(lines[len(lines)-1], ":") + if len(parts) >= 2 { + verifyOutput = strings.TrimSpace(parts[1]) + } + } + + if verifyOutput == "OK" { + return "verified by a certificate authority" + } + + return "self signed" +} + +func reportSSLHostnames(appName string) string { + if !isSSLEnabled(appName) { + return "" + } + + hostnameSet := map[string]bool{} + + subjectResult, err := common.CallExecCommand(common.ExecCommandInput{ + Command: "openssl", + Args: []string{"x509", "-in", filepath.Join(certTLSPath(appName), "server.crt"), "-noout", "-subject"}, + }) + if err == nil { + for _, part := range strings.Split(subjectResult.StdoutContents(), "/") { + if strings.Contains(part, "CN=") && len(part) > 3 { + hostnameSet[part[3:]] = true + } + } + } + + textLines := strings.Split(opensslCertText(appName), "\n") + for i, line := range textLines { + if strings.Contains(line, "509v3 Subject Alternative Name:") && i+1 < len(textLines) { + sanLine := dnsPrefixRegex.ReplaceAllString(textLines[i+1], "") + for _, name := range strings.Split(sanLine, ",") { + name = strings.TrimSpace(name) + if name != "" { + hostnameSet[name] = true + } + } + } + } + + hostnames := []string{} + for name := range hostnameSet { + hostnames = append(hostnames, name) + } + sort.Strings(hostnames) + + return strings.Join(hostnames, " ") +} diff --git a/plugins/certs/src/subcommands/subcommands.go b/plugins/certs/src/subcommands/subcommands.go new file mode 100644 index 000000000..4027b90b8 --- /dev/null +++ b/plugins/certs/src/subcommands/subcommands.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + certs "github.com/dokku/dokku/plugins/certs" + "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("certs:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + reportArgs, flagErr := common.ParseReportArgs("certs", os.Args[2:]) + if flagErr == nil { + args.Parse(reportArgs.OSArgs) + appName := args.Arg(0) + if reportArgs.IsGlobal { + appName = "--global" + } + err = certs.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/certs/src/triggers/triggers.go b/plugins/certs/src/triggers/triggers.go new file mode 100644 index 000000000..245fa8038 --- /dev/null +++ b/plugins/certs/src/triggers/triggers.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + certs "github.com/dokku/dokku/plugins/certs" + "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 = certs.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/certs/subcommands.go b/plugins/certs/subcommands.go new file mode 100644 index 000000000..56521baf3 --- /dev/null +++ b/plugins/certs/subcommands.go @@ -0,0 +1,29 @@ +package certs + +import ( + "errors" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandReport displays a certs 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/certs/subcommands/report b/plugins/certs/subcommands/report deleted file mode 100755 index 054bbf7ca..000000000 --- a/plugins/certs/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/certs/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-certs-report "$@" From 3fee5b881aa52239328ddfb0204336a1f9c18451 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 7 Jul 2026 07:02:38 -0400 Subject: [PATCH 5/9] feat: port git :report subcommand to golang The bash :report implementation for the git plugin is replaced with a compiled golang binary. Property, computed and global keys are collected in parallel, while the sha and last-updated-at keys still shell out to git and stat the deploy branch ref so their values are unchanged. The non-report git helpers such as fn-git-cmd and the computed deploy-branch and keep-git-dir getters remain in place for the build pipeline. --- go.work | 1 + plugins/git/.gitignore | 4 + plugins/git/Makefile | 13 ++ plugins/git/go.mod | 29 ++++ plugins/git/go.sum | 55 ++++++++ plugins/git/internal-functions | 106 -------------- plugins/git/report | 6 - plugins/git/report.go | 154 +++++++++++++++++++++ plugins/git/src/subcommands/subcommands.go | 40 ++++++ plugins/git/src/triggers/triggers.go | 31 +++++ plugins/git/subcommands.go | 29 ++++ plugins/git/subcommands/report | 6 - 12 files changed, 356 insertions(+), 118 deletions(-) create mode 100644 plugins/git/.gitignore create mode 100644 plugins/git/Makefile create mode 100644 plugins/git/go.mod create mode 100644 plugins/git/go.sum mode change 100755 => 100644 plugins/git/internal-functions delete mode 100755 plugins/git/report create mode 100644 plugins/git/report.go create mode 100644 plugins/git/src/subcommands/subcommands.go create mode 100644 plugins/git/src/triggers/triggers.go create mode 100644 plugins/git/subcommands.go delete mode 100755 plugins/git/subcommands/report diff --git a/go.work b/go.work index 2aa88dabc..a5a83cf18 100644 --- a/go.work +++ b/go.work @@ -19,6 +19,7 @@ use ( ./plugins/cron ./plugins/docker-options ./plugins/domains + ./plugins/git ./plugins/haproxy-vhosts ./plugins/logs ./plugins/network diff --git a/plugins/git/.gitignore b/plugins/git/.gitignore new file mode 100644 index 000000000..dac9d7731 --- /dev/null +++ b/plugins/git/.gitignore @@ -0,0 +1,4 @@ +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/git/Makefile b/plugins/git/Makefile new file mode 100644 index 000000000..4a17c4b2a --- /dev/null +++ b/plugins/git/Makefile @@ -0,0 +1,13 @@ +GOARCH ?= amd64 +TRIGGERS = triggers/report +BUILD = report-subcommand triggers +PLUGIN_NAME = git + +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/git/go.mod b/plugins/git/go.mod new file mode 100644 index 000000000..0727b8524 --- /dev/null +++ b/plugins/git/go.mod @@ -0,0 +1,29 @@ +module github.com/dokku/dokku/plugins/git + +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/git/go.sum b/plugins/git/go.sum new file mode 100644 index 000000000..5a5292407 --- /dev/null +++ b/plugins/git/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/git/internal-functions b/plugins/git/internal-functions old mode 100755 new mode 100644 index 11d4f367b..7fcca2bf4 --- a/plugins/git/internal-functions +++ b/plugins/git/internal-functions @@ -328,112 +328,6 @@ cmd-git-public-key() { fn-git-auth-error } -cmd-git-report() { - declare desc="displays a git report for one or more apps" - declare cmd="git: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-git-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - elif [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-git-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true - done - else - cmd-git-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - fi -} - -cmd-git-report-single() { - declare APP="$1" INFO_FLAG="$2" FORMAT="${3:-stdout}" - local APP_ROOT="$DOKKU_ROOT/$APP" - if [[ "$INFO_FLAG" == "true" ]]; then - INFO_FLAG="" - fi - local flag_map=() - if [[ "$APP" == "--global" ]]; then - flag_map=( - "--git-computed-archive-max-files: $(fn-archive-computed-max-files)" - "--git-computed-archive-max-size: $(fn-archive-computed-max-size)" - "--git-computed-deploy-branch: $(fn-git-computed-deploy-branch "$APP")" - "--git-computed-keep-git-dir: $(fn-git-computed-keep-git-dir "$APP")" - "--git-global-archive-max-files: $(fn-plugin-property-get "git" "--global" "archive-max-files" "")" - "--git-global-archive-max-size: $(fn-plugin-property-get "git" "--global" "archive-max-size" "")" - "--git-global-deploy-branch: $(fn-plugin-property-get "git" "--global" "deploy-branch" "")" - "--git-global-keep-git-dir: $(fn-plugin-property-get "git" "--global" "keep-git-dir" "")" - ) - else - verify_app_name "$APP" - flag_map=( - "--git-computed-archive-max-files: $(fn-archive-computed-max-files)" - "--git-computed-archive-max-size: $(fn-archive-computed-max-size)" - "--git-computed-deploy-branch: $(fn-git-computed-deploy-branch "$APP")" - "--git-computed-keep-git-dir: $(fn-git-computed-keep-git-dir "$APP")" - "--git-deploy-branch: $(fn-plugin-property-get "git" "$APP" "deploy-branch" "")" - "--git-global-archive-max-files: $(fn-plugin-property-get "git" "--global" "archive-max-files" "")" - "--git-global-archive-max-size: $(fn-plugin-property-get "git" "--global" "archive-max-size" "")" - "--git-global-deploy-branch: $(fn-plugin-property-get "git" "--global" "deploy-branch" "")" - "--git-global-keep-git-dir: $(fn-plugin-property-get "git" "--global" "keep-git-dir" "")" - "--git-keep-git-dir: $(fn-plugin-property-get "git" "$APP" "keep-git-dir" "")" - "--git-rev-env-var: $(fn-plugin-property-get "git" "$APP" "rev-env-var" "GIT_REV")" - "--git-sha: $(fn-git-cmd "$APP_ROOT" rev-parse HEAD 2>/dev/null || false)" - "--git-source-image: $(fn-git-source-image "$APP")" - "--git-last-updated-at: $(fn-git-last-updated-at "$APP")" - ) - fi - - fn-report-validate-format "$FORMAT" "$INFO_FLAG" - - if [[ "$FORMAT" == "json" ]]; then - fn-report-emit-json flag_map "git" - return - fi - - if [[ -z "$INFO_FLAG" ]]; then - if [[ "$APP" == "--global" ]]; then - dokku_log_info2_quiet "global git information" - else - dokku_log_info2_quiet "${APP} git 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-git-status() { declare desc="check the git status output for an app" local cmd="git:status" diff --git a/plugins/git/report b/plugins/git/report deleted file mode 100755 index ade453679..000000000 --- a/plugins/git/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/git/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-git-report-single "$@" diff --git a/plugins/git/report.go b/plugins/git/report.go new file mode 100644 index 000000000..1e5d0b255 --- /dev/null +++ b/plugins/git/report.go @@ -0,0 +1,154 @@ +package git + +import ( + "os" + "path/filepath" + "strconv" + + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the git 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 + } + } + + var flags map[string]common.ReportFunc + if appName == "--global" { + flags = map[string]common.ReportFunc{ + "--git-computed-archive-max-files": reportComputedArchiveMaxFiles, + "--git-computed-archive-max-size": reportComputedArchiveMaxSize, + "--git-computed-deploy-branch": reportComputedDeployBranch, + "--git-computed-keep-git-dir": reportComputedKeepGitDir, + "--git-global-archive-max-files": reportGlobalArchiveMaxFiles, + "--git-global-archive-max-size": reportGlobalArchiveMaxSize, + "--git-global-deploy-branch": reportGlobalDeployBranch, + "--git-global-keep-git-dir": reportGlobalKeepGitDir, + } + } else { + flags = map[string]common.ReportFunc{ + "--git-computed-archive-max-files": reportComputedArchiveMaxFiles, + "--git-computed-archive-max-size": reportComputedArchiveMaxSize, + "--git-computed-deploy-branch": reportComputedDeployBranch, + "--git-computed-keep-git-dir": reportComputedKeepGitDir, + "--git-deploy-branch": reportDeployBranch, + "--git-global-archive-max-files": reportGlobalArchiveMaxFiles, + "--git-global-archive-max-size": reportGlobalArchiveMaxSize, + "--git-global-deploy-branch": reportGlobalDeployBranch, + "--git-global-keep-git-dir": reportGlobalKeepGitDir, + "--git-keep-git-dir": reportKeepGitDir, + "--git-last-updated-at": reportLastUpdatedAt, + "--git-rev-env-var": reportRevEnvVar, + "--git-sha": reportSha, + "--git-source-image": reportSourceImage, + } + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "git", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: false, + }) +} + +func reportGlobalArchiveMaxFiles(appName string) string { + return common.PropertyGet("git", "--global", "archive-max-files") +} + +func reportComputedArchiveMaxFiles(appName string) string { + return common.PropertyGetDefault("git", "--global", "archive-max-files", "10000") +} + +func reportGlobalArchiveMaxSize(appName string) string { + return common.PropertyGet("git", "--global", "archive-max-size") +} + +func reportComputedArchiveMaxSize(appName string) string { + return common.PropertyGetDefault("git", "--global", "archive-max-size", "1073741824") +} + +func reportDeployBranch(appName string) string { + return common.PropertyGet("git", appName, "deploy-branch") +} + +func reportGlobalDeployBranch(appName string) string { + return common.PropertyGet("git", "--global", "deploy-branch") +} + +func reportComputedDeployBranch(appName string) string { + if value := common.PropertyGet("git", appName, "deploy-branch"); value != "" { + return value + } + if value := common.PropertyGet("git", "--global", "deploy-branch"); value != "" { + return value + } + + return "master" +} + +func reportKeepGitDir(appName string) string { + return common.PropertyGet("git", appName, "keep-git-dir") +} + +func reportGlobalKeepGitDir(appName string) string { + return common.PropertyGet("git", "--global", "keep-git-dir") +} + +func reportComputedKeepGitDir(appName string) string { + if value := common.PropertyGet("git", appName, "keep-git-dir"); value != "" { + return value + } + if value := common.PropertyGet("git", "--global", "keep-git-dir"); value != "" { + return value + } + + return "false" +} + +func reportRevEnvVar(appName string) string { + return common.PropertyGetDefault("git", appName, "rev-env-var", "GIT_REV") +} + +func reportSourceImage(appName string) string { + return common.PropertyGet("git", appName, "source-image") +} + +func reportSha(appName string) string { + appRoot := filepath.Join(common.MustGetEnv("DOKKU_ROOT"), appName) + result, err := common.CallExecCommand(common.ExecCommandInput{ + Command: "git", + Args: []string{"rev-parse", "HEAD"}, + WorkingDirectory: appRoot, + }) + if err != nil { + return "" + } + + return result.StdoutContents() +} + +func reportLastUpdatedAt(appName string) string { + branch := reportComputedDeployBranch(appName) + headFile := filepath.Join(common.MustGetEnv("DOKKU_ROOT"), appName, "refs", "heads", branch) + info, err := os.Stat(headFile) + if err != nil { + return "" + } + + return strconv.FormatInt(info.ModTime().Unix(), 10) +} diff --git a/plugins/git/src/subcommands/subcommands.go b/plugins/git/src/subcommands/subcommands.go new file mode 100644 index 000000000..1ae4d949b --- /dev/null +++ b/plugins/git/src/subcommands/subcommands.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + git "github.com/dokku/dokku/plugins/git" + + 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("git:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + reportArgs, flagErr := common.ParseReportArgs("git", os.Args[2:]) + if flagErr == nil { + args.Parse(reportArgs.OSArgs) + appName := args.Arg(0) + if reportArgs.IsGlobal { + appName = "--global" + } + err = git.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/git/src/triggers/triggers.go b/plugins/git/src/triggers/triggers.go new file mode 100644 index 000000000..966a9a55b --- /dev/null +++ b/plugins/git/src/triggers/triggers.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + git "github.com/dokku/dokku/plugins/git" +) + +// 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 = git.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/git/subcommands.go b/plugins/git/subcommands.go new file mode 100644 index 000000000..5b02536cd --- /dev/null +++ b/plugins/git/subcommands.go @@ -0,0 +1,29 @@ +package git + +import ( + "errors" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandReport displays a git 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/git/subcommands/report b/plugins/git/subcommands/report deleted file mode 100755 index 35fc66a87..000000000 --- a/plugins/git/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/git/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-git-report "$@" From fe11d508d36673f90cede03d2e7d9abf9fc03603 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 7 Jul 2026 07:06:24 -0400 Subject: [PATCH 6/9] feat: port scheduler-docker-local :report subcommand to golang The bash :report implementation for the scheduler-docker-local plugin is replaced with a compiled golang binary. The plugin already shipped golang code, so the report subcommand is added alongside its existing triggers binary, and the init-process and parallel-schedule-count helpers remain in bash for the deploy pipeline. --- plugins/scheduler-docker-local/.gitignore | 3 + plugins/scheduler-docker-local/Makefile | 11 ++- .../scheduler-docker-local/internal-functions | 92 ------------------- plugins/scheduler-docker-local/report | 6 -- plugins/scheduler-docker-local/report.go | 91 ++++++++++++++++++ .../src/subcommands/subcommands.go | 40 ++++++++ .../src/triggers/triggers.go | 3 + plugins/scheduler-docker-local/subcommands.go | 29 ++++++ .../scheduler-docker-local/subcommands/report | 6 -- 9 files changed, 175 insertions(+), 106 deletions(-) mode change 100755 => 100644 plugins/scheduler-docker-local/internal-functions delete mode 100755 plugins/scheduler-docker-local/report create mode 100644 plugins/scheduler-docker-local/report.go create mode 100644 plugins/scheduler-docker-local/src/subcommands/subcommands.go create mode 100644 plugins/scheduler-docker-local/subcommands.go delete mode 100755 plugins/scheduler-docker-local/subcommands/report diff --git a/plugins/scheduler-docker-local/.gitignore b/plugins/scheduler-docker-local/.gitignore index 0e22b8969..4c57fe4bf 100644 --- a/plugins/scheduler-docker-local/.gitignore +++ b/plugins/scheduler-docker-local/.gitignore @@ -1,5 +1,8 @@ /triggers/* /triggers /cron-* +/report +/report-subcommand /scheduler-cron-write /scheduler-storage-exec +/subcommands/report diff --git a/plugins/scheduler-docker-local/Makefile b/plugins/scheduler-docker-local/Makefile index e781a2d7f..841d9ae0c 100644 --- a/plugins/scheduler-docker-local/Makefile +++ b/plugins/scheduler-docker-local/Makefile @@ -1,5 +1,12 @@ -TRIGGERS = triggers/scheduler-cron-write triggers/scheduler-storage-exec -BUILD = triggers +TRIGGERS = triggers/report triggers/scheduler-cron-write triggers/scheduler-storage-exec +BUILD = report-subcommand triggers PLUGIN_NAME = scheduler-docker-local +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/scheduler-docker-local/internal-functions b/plugins/scheduler-docker-local/internal-functions old mode 100755 new mode 100644 index 3bf7115f7..e429438f5 --- a/plugins/scheduler-docker-local/internal-functions +++ b/plugins/scheduler-docker-local/internal-functions @@ -5,98 +5,6 @@ source "$PLUGIN_AVAILABLE_PATH/config/functions" set -eo pipefail [[ $DOKKU_TRACE ]] && set -x -cmd-scheduler-docker-local-report() { - declare desc="displays a scheduler-docker-local report for one or more apps" - declare cmd="scheduler-docker-local: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-scheduler-docker-local-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - elif [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-scheduler-docker-local-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true - done - else - cmd-scheduler-docker-local-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - fi -} - -cmd-scheduler-docker-local-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=( - "--scheduler-docker-local-computed-init-process: $(fn-scheduler-docker-local-computed-init-process "$APP")" - "--scheduler-docker-local-computed-parallel-schedule-count: $(fn-scheduler-docker-local-computed-parallel-schedule-count "$APP")" - "--scheduler-docker-local-global-init-process: $(fn-scheduler-docker-local-global-init-process "$APP")" - "--scheduler-docker-local-global-parallel-schedule-count: $(fn-scheduler-docker-local-global-parallel-schedule-count "$APP")" - ) - else - verify_app_name "$APP" - flag_map=( - "--scheduler-docker-local-computed-init-process: $(fn-scheduler-docker-local-computed-init-process "$APP")" - "--scheduler-docker-local-computed-parallel-schedule-count: $(fn-scheduler-docker-local-computed-parallel-schedule-count "$APP")" - "--scheduler-docker-local-global-init-process: $(fn-scheduler-docker-local-global-init-process "$APP")" - "--scheduler-docker-local-global-parallel-schedule-count: $(fn-scheduler-docker-local-global-parallel-schedule-count "$APP")" - "--scheduler-docker-local-init-process: $(fn-scheduler-docker-local-init-process "$APP")" - "--scheduler-docker-local-parallel-schedule-count: $(fn-scheduler-docker-local-parallel-schedule-count "$APP")" - ) - fi - - fn-report-validate-format "$FORMAT" "$INFO_FLAG" - - if [[ "$FORMAT" == "json" ]]; then - fn-report-emit-json flag_map "scheduler-docker-local" - return - fi - - if [[ -z "$INFO_FLAG" ]]; then - if [[ "$APP" == "--global" ]]; then - dokku_log_info2_quiet "global scheduler-docker-local information" - else - dokku_log_info2_quiet "${APP} scheduler-docker-local 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 -} - fn-scheduler-docker-local-init-process() { declare APP="$1" fn-plugin-property-get-default "scheduler-docker-local" "$APP" "init-process" "" diff --git a/plugins/scheduler-docker-local/report b/plugins/scheduler-docker-local/report deleted file mode 100755 index 5403f8630..000000000 --- a/plugins/scheduler-docker-local/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/scheduler-docker-local/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-scheduler-docker-local-report-single "$@" diff --git a/plugins/scheduler-docker-local/report.go b/plugins/scheduler-docker-local/report.go new file mode 100644 index 000000000..a933a2d8d --- /dev/null +++ b/plugins/scheduler-docker-local/report.go @@ -0,0 +1,91 @@ +package schedulerdockerlocal + +import ( + "github.com/dokku/dokku/plugins/common" +) + +// ReportSingleApp is an internal function that displays the scheduler-docker-local 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 + } + } + + var flags map[string]common.ReportFunc + if appName == "--global" { + flags = map[string]common.ReportFunc{ + "--scheduler-docker-local-computed-init-process": reportComputedInitProcess, + "--scheduler-docker-local-computed-parallel-schedule-count": reportComputedParallelScheduleCount, + "--scheduler-docker-local-global-init-process": reportGlobalInitProcess, + "--scheduler-docker-local-global-parallel-schedule-count": reportGlobalParallelScheduleCount, + } + } else { + flags = map[string]common.ReportFunc{ + "--scheduler-docker-local-computed-init-process": reportComputedInitProcess, + "--scheduler-docker-local-computed-parallel-schedule-count": reportComputedParallelScheduleCount, + "--scheduler-docker-local-global-init-process": reportGlobalInitProcess, + "--scheduler-docker-local-global-parallel-schedule-count": reportGlobalParallelScheduleCount, + "--scheduler-docker-local-init-process": reportInitProcess, + "--scheduler-docker-local-parallel-schedule-count": reportParallelScheduleCount, + } + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "scheduler-docker-local", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: false, + }) +} + +func reportInitProcess(appName string) string { + return common.PropertyGet("scheduler-docker-local", appName, "init-process") +} + +func reportGlobalInitProcess(appName string) string { + return common.PropertyGet("scheduler-docker-local", "--global", "init-process") +} + +func reportComputedInitProcess(appName string) string { + value := reportInitProcess(appName) + if value == "" { + value = reportGlobalInitProcess(appName) + } + if value == "" { + value = "true" + } + + return value +} + +func reportParallelScheduleCount(appName string) string { + return common.PropertyGet("scheduler-docker-local", appName, "parallel-schedule-count") +} + +func reportGlobalParallelScheduleCount(appName string) string { + return common.PropertyGet("scheduler-docker-local", "--global", "parallel-schedule-count") +} + +func reportComputedParallelScheduleCount(appName string) string { + value := reportParallelScheduleCount(appName) + if value == "" { + value = reportGlobalParallelScheduleCount(appName) + } + if value == "" { + value = "1" + } + + return value +} diff --git a/plugins/scheduler-docker-local/src/subcommands/subcommands.go b/plugins/scheduler-docker-local/src/subcommands/subcommands.go new file mode 100644 index 000000000..9ca2708bc --- /dev/null +++ b/plugins/scheduler-docker-local/src/subcommands/subcommands.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + schedulerdockerlocal "github.com/dokku/dokku/plugins/scheduler-docker-local" + + 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("scheduler-docker-local:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + reportArgs, flagErr := common.ParseReportArgs("scheduler-docker-local", os.Args[2:]) + if flagErr == nil { + args.Parse(reportArgs.OSArgs) + appName := args.Arg(0) + if reportArgs.IsGlobal { + appName = "--global" + } + err = schedulerdockerlocal.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/scheduler-docker-local/src/triggers/triggers.go b/plugins/scheduler-docker-local/src/triggers/triggers.go index 2e2c4ff61..598c8e023 100644 --- a/plugins/scheduler-docker-local/src/triggers/triggers.go +++ b/plugins/scheduler-docker-local/src/triggers/triggers.go @@ -47,6 +47,9 @@ func main() { AsUser: *asUser, Command: cmd, }) + case "report": + appName := flag.Arg(0) + err = schedulerdockerlocal.ReportSingleApp(appName, "", "") default: err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) } diff --git a/plugins/scheduler-docker-local/subcommands.go b/plugins/scheduler-docker-local/subcommands.go new file mode 100644 index 000000000..bd47efb55 --- /dev/null +++ b/plugins/scheduler-docker-local/subcommands.go @@ -0,0 +1,29 @@ +package schedulerdockerlocal + +import ( + "errors" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandReport displays a scheduler-docker-local 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/scheduler-docker-local/subcommands/report b/plugins/scheduler-docker-local/subcommands/report deleted file mode 100755 index a9787c80c..000000000 --- a/plugins/scheduler-docker-local/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/scheduler-docker-local/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-scheduler-docker-local-report "$@" From 7ee882c4e7677917f5157b99c733b5debb9fec31 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 7 Jul 2026 07:12:59 -0400 Subject: [PATCH 7/9] feat: port nginx :report subcommand to golang The bash :report implementation for the nginx-vhosts plugin is replaced with a compiled golang binary that reuses the plugin's existing golang property getters, so every raw, global and computed key is unchanged while collection runs in parallel and json is marshalled directly. The global report keeps its existing behaviour of surfacing only the global keys. --- plugins/nginx-vhosts/.gitignore | 4 + plugins/nginx-vhosts/Makefile | 10 +- plugins/nginx-vhosts/command-functions | 177 ------------------ plugins/nginx-vhosts/report | 6 - plugins/nginx-vhosts/report.go | 114 +++++++++++ .../src/subcommands/subcommands.go | 40 ++++ plugins/nginx-vhosts/src/triggers/triggers.go | 31 +++ plugins/nginx-vhosts/subcommands.go | 29 +++ plugins/nginx-vhosts/subcommands/report | 6 - 9 files changed, 227 insertions(+), 190 deletions(-) mode change 100755 => 100644 plugins/nginx-vhosts/command-functions delete mode 100755 plugins/nginx-vhosts/report create mode 100644 plugins/nginx-vhosts/report.go create mode 100644 plugins/nginx-vhosts/src/subcommands/subcommands.go create mode 100644 plugins/nginx-vhosts/src/triggers/triggers.go create mode 100644 plugins/nginx-vhosts/subcommands.go delete mode 100755 plugins/nginx-vhosts/subcommands/report diff --git a/plugins/nginx-vhosts/.gitignore b/plugins/nginx-vhosts/.gitignore index ee69b9339..0750942c3 100644 --- a/plugins/nginx-vhosts/.gitignore +++ b/plugins/nginx-vhosts/.gitignore @@ -1,2 +1,6 @@ /pagesize /nginx-property +/report +/report-subcommand +/triggers +/subcommands/report diff --git a/plugins/nginx-vhosts/Makefile b/plugins/nginx-vhosts/Makefile index 504458a93..8c14028f8 100644 --- a/plugins/nginx-vhosts/Makefile +++ b/plugins/nginx-vhosts/Makefile @@ -1,7 +1,15 @@ GOARCH ?= amd64 -BUILD = pagesize nginx-property +TRIGGERS = triggers/report +BUILD = pagesize nginx-property report-subcommand triggers PLUGIN_NAME = nginx-vhosts +clean-report-subcommand: + rm -rf report-subcommand + +report-subcommand: clean-report-subcommand src/subcommands/subcommands.go + GOARCH=$(GOARCH) go build -mod=readonly -ldflags="-s -w" $(GO_ARGS) -o report-subcommand src/subcommands/subcommands.go + ln -sf ../report-subcommand subcommands/report + clean-pagesize: rm -rf pagesize diff --git a/plugins/nginx-vhosts/command-functions b/plugins/nginx-vhosts/command-functions old mode 100755 new mode 100644 index 961303970..cd54f4f07 --- a/plugins/nginx-vhosts/command-functions +++ b/plugins/nginx-vhosts/command-functions @@ -6,183 +6,6 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/internal-functions" -cmd-nginx-report() { - declare desc="displays a nginx report for one or more apps" - declare cmd="nginx:report" - [[ "$1" == "$cmd" ]] && shift 1 - fn-report-parse-args "$@" - set -- "${REPORT_ARGS[@]}" - - declare APP="${1:-}" INFO_FLAG="${2:-}" - - if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then - INFO_FLAG="$APP" - APP="" - fi - - if [[ "$REPORT_IS_GLOBAL" == "true" ]]; then - APP="--global" - fi - - if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then - INFO_FLAG="true" - fi - - if [[ "$APP" == "--global" ]]; then - cmd-nginx-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - elif [[ -z "$APP" ]]; then - for app in $(dokku_apps); do - cmd-nginx-report-single "$app" "$INFO_FLAG" "$REPORT_FORMAT" | tee || true - done - else - cmd-nginx-report-single "$APP" "$INFO_FLAG" "$REPORT_FORMAT" - fi -} - -cmd-nginx-report-single() { - declare APP="$1" INFO_FLAG="$2" FORMAT="${3:-stdout}" - if [[ "$INFO_FLAG" == "true" ]]; then - INFO_FLAG="" - fi - if [[ "$APP" != "--global" ]]; then - verify_app_name "$APP" - fi - local flag_map=( - "--nginx-access-log-format: $(fn-nginx-access-log-format "$APP")" - "--nginx-computed-access-log-format: $(fn-nginx-computed-access-log-format "$APP")" - "--nginx-global-access-log-format: $(fn-nginx-global-access-log-format "$APP")" - "--nginx-access-log-path: $(fn-nginx-access-log-path "$APP")" - "--nginx-computed-access-log-path: $(fn-nginx-computed-access-log-path "$APP")" - "--nginx-global-access-log-path: $(fn-nginx-global-access-log-path "$APP")" - "--nginx-bind-address-ipv4: $(fn-nginx-bind-address-ipv4 "$APP")" - "--nginx-computed-bind-address-ipv4: $(fn-nginx-computed-bind-address-ipv4 "$APP")" - "--nginx-global-bind-address-ipv4: $(fn-nginx-global-bind-address-ipv4 "$APP")" - "--nginx-bind-address-ipv6: $(fn-nginx-bind-address-ipv6 "$APP")" - "--nginx-computed-bind-address-ipv6: $(fn-nginx-computed-bind-address-ipv6 "$APP")" - "--nginx-global-bind-address-ipv6: $(fn-nginx-global-bind-address-ipv6 "$APP")" - "--nginx-client-body-timeout: $(fn-nginx-client-body-timeout "$APP")" - "--nginx-computed-client-body-timeout: $(fn-nginx-computed-client-body-timeout "$APP")" - "--nginx-global-client-body-timeout: $(fn-nginx-global-client-body-timeout "$APP")" - "--nginx-client-header-timeout: $(fn-nginx-client-header-timeout "$APP")" - "--nginx-computed-client-header-timeout: $(fn-nginx-computed-client-header-timeout "$APP")" - "--nginx-global-client-header-timeout: $(fn-nginx-global-client-header-timeout "$APP")" - "--nginx-client-max-body-size: $(fn-nginx-client-max-body-size "$APP")" - "--nginx-computed-client-max-body-size: $(fn-nginx-computed-client-max-body-size "$APP")" - "--nginx-global-client-max-body-size: $(fn-nginx-global-client-max-body-size "$APP")" - "--nginx-disable-custom-config: $(fn-nginx-disable-custom-config "$APP")" - "--nginx-computed-disable-custom-config: $(fn-nginx-computed-disable-custom-config "$APP")" - "--nginx-global-disable-custom-config: $(fn-nginx-global-disable-custom-config "$APP")" - "--nginx-error-log-path: $(fn-nginx-error-log-path "$APP")" - "--nginx-computed-error-log-path: $(fn-nginx-computed-error-log-path "$APP")" - "--nginx-global-error-log-path: $(fn-nginx-global-error-log-path "$APP")" - "--nginx-hsts-include-subdomains: $(fn-nginx-hsts-include-subdomains "$APP")" - "--nginx-computed-hsts-include-subdomains: $(fn-nginx-computed-hsts-include-subdomains "$APP")" - "--nginx-global-hsts-include-subdomains: $(fn-nginx-global-hsts-include-subdomains "$APP")" - "--nginx-hsts-max-age: $(fn-nginx-hsts-max-age "$APP")" - "--nginx-computed-hsts-max-age: $(fn-nginx-computed-hsts-max-age "$APP")" - "--nginx-global-hsts-max-age: $(fn-nginx-global-hsts-max-age "$APP")" - "--nginx-hsts-preload: $(fn-nginx-hsts-preload "$APP")" - "--nginx-computed-hsts-preload: $(fn-nginx-computed-hsts-preload "$APP")" - "--nginx-global-hsts-preload: $(fn-nginx-global-hsts-preload "$APP")" - "--nginx-hsts: $(fn-nginx-hsts "$APP")" - "--nginx-computed-hsts: $(fn-nginx-computed-hsts "$APP")" - "--nginx-global-hsts: $(fn-nginx-global-hsts "$APP")" - "--nginx-last-visited-at: $(fn-nginx-vhosts-last-visited-at "$APP")" - "--nginx-keepalive-timeout: $(fn-nginx-keepalive-timeout "$APP")" - "--nginx-computed-keepalive-timeout: $(fn-nginx-computed-keepalive-timeout "$APP")" - "--nginx-global-keepalive-timeout: $(fn-nginx-global-keepalive-timeout "$APP")" - "--nginx-lingering-timeout: $(fn-nginx-lingering-timeout "$APP")" - "--nginx-computed-lingering-timeout: $(fn-nginx-computed-lingering-timeout "$APP")" - "--nginx-global-lingering-timeout: $(fn-nginx-global-lingering-timeout "$APP")" - "--nginx-nginx-conf-sigil-path: $(fn-nginx-nginx-conf-sigil-path "$APP")" - "--nginx-computed-nginx-conf-sigil-path: $(fn-nginx-computed-nginx-conf-sigil-path "$APP")" - "--nginx-global-nginx-conf-sigil-path: $(fn-nginx-global-nginx-conf-sigil-path "$APP")" - "--nginx-nginx-service-command: $(fn-nginx-nginx-service-command "$APP")" - "--nginx-computed-nginx-service-command: $(fn-nginx-computed-nginx-service-command "$APP")" - "--nginx-global-nginx-service-command: $(fn-nginx-global-nginx-service-command "$APP")" - "--nginx-proxy-buffer-size: $(fn-nginx-proxy-buffer-size "$APP")" - "--nginx-computed-proxy-buffer-size: $(fn-nginx-computed-proxy-buffer-size "$APP")" - "--nginx-global-proxy-buffer-size: $(fn-nginx-global-proxy-buffer-size "$APP")" - "--nginx-proxy-buffering: $(fn-nginx-proxy-buffering "$APP")" - "--nginx-computed-proxy-buffering: $(fn-nginx-computed-proxy-buffering "$APP")" - "--nginx-global-proxy-buffering: $(fn-nginx-global-proxy-buffering "$APP")" - "--nginx-proxy-buffers: $(fn-nginx-proxy-buffers "$APP")" - "--nginx-computed-proxy-buffers: $(fn-nginx-computed-proxy-buffers "$APP")" - "--nginx-global-proxy-buffers: $(fn-nginx-global-proxy-buffers "$APP")" - "--nginx-proxy-busy-buffers-size: $(fn-nginx-proxy-busy-buffers-size "$APP")" - "--nginx-computed-proxy-busy-buffers-size: $(fn-nginx-computed-proxy-busy-buffers-size "$APP")" - "--nginx-global-proxy-busy-buffers-size: $(fn-nginx-global-proxy-busy-buffers-size "$APP")" - "--nginx-proxy-connect-timeout: $(fn-nginx-proxy-connect-timeout "$APP")" - "--nginx-computed-proxy-connect-timeout: $(fn-nginx-computed-proxy-connect-timeout "$APP")" - "--nginx-global-proxy-connect-timeout: $(fn-nginx-global-proxy-connect-timeout "$APP")" - "--nginx-proxy-read-timeout: $(fn-nginx-proxy-read-timeout "$APP")" - "--nginx-computed-proxy-read-timeout: $(fn-nginx-computed-proxy-read-timeout "$APP")" - "--nginx-global-proxy-read-timeout: $(fn-nginx-global-proxy-read-timeout "$APP")" - "--nginx-proxy-send-timeout: $(fn-nginx-proxy-send-timeout "$APP")" - "--nginx-computed-proxy-send-timeout: $(fn-nginx-computed-proxy-send-timeout "$APP")" - "--nginx-global-proxy-send-timeout: $(fn-nginx-global-proxy-send-timeout "$APP")" - "--nginx-send-timeout: $(fn-nginx-send-timeout "$APP")" - "--nginx-computed-send-timeout: $(fn-nginx-computed-send-timeout "$APP")" - "--nginx-global-send-timeout: $(fn-nginx-global-send-timeout "$APP")" - "--nginx-proxy-keepalive: $(fn-nginx-proxy-keepalive "$APP")" - "--nginx-computed-proxy-keepalive: $(fn-nginx-computed-proxy-keepalive "$APP")" - "--nginx-global-proxy-keepalive: $(fn-nginx-global-proxy-keepalive "$APP")" - "--nginx-underscore-in-headers: $(fn-nginx-underscore-in-headers "$APP")" - "--nginx-computed-underscore-in-headers: $(fn-nginx-computed-underscore-in-headers "$APP")" - "--nginx-global-underscore-in-headers: $(fn-nginx-global-underscore-in-headers "$APP")" - "--nginx-x-forwarded-for-value: $(fn-nginx-x-forwarded-for-value "$APP")" - "--nginx-computed-x-forwarded-for-value: $(fn-nginx-computed-x-forwarded-for-value "$APP")" - "--nginx-global-x-forwarded-for-value: $(fn-nginx-global-x-forwarded-for-value "$APP")" - "--nginx-x-forwarded-port-value: $(fn-nginx-x-forwarded-port-value "$APP")" - "--nginx-computed-x-forwarded-port-value: $(fn-nginx-computed-x-forwarded-port-value "$APP")" - "--nginx-global-x-forwarded-port-value: $(fn-nginx-global-x-forwarded-port-value "$APP")" - "--nginx-x-forwarded-proto-value: $(fn-nginx-x-forwarded-proto-value "$APP")" - "--nginx-computed-x-forwarded-proto-value: $(fn-nginx-computed-x-forwarded-proto-value "$APP")" - "--nginx-global-x-forwarded-proto-value: $(fn-nginx-global-x-forwarded-proto-value "$APP")" - "--nginx-x-forwarded-ssl: $(fn-nginx-x-forwarded-ssl "$APP")" - "--nginx-computed-x-forwarded-ssl: $(fn-nginx-computed-x-forwarded-ssl "$APP")" - "--nginx-global-x-forwarded-ssl: $(fn-nginx-global-x-forwarded-ssl "$APP")" - ) - - if [[ "$APP" == "--global" ]]; then - fn-report-filter-global flag_map - fi - - fn-report-validate-format "$FORMAT" "$INFO_FLAG" - - if [[ "$FORMAT" == "json" ]]; then - fn-report-emit-json flag_map "nginx" - return - fi - - if [[ -z "$INFO_FLAG" ]]; then - if [[ "$APP" == "--global" ]]; then - dokku_log_info2_quiet "global nginx information" - else - dokku_log_info2_quiet "${APP} nginx information" - fi - for flag in "${flag_map[@]}"; do - key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" - dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")" - done - else - local match=false - for flag in "${flag_map[@]}"; do - valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" - if [[ "$flag" == "${INFO_FLAG}:"* ]]; then - value=${flag#*: } - size="${#value}" - if [[ "$size" -ne 0 ]]; then - echo "$value" && match=true - else - match=true - fi - fi - done - [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" - fi -} - cmd-nginx-show-config() { declare desc="display app nginx config" declare cmd="nginx:show-config" diff --git a/plugins/nginx-vhosts/report b/plugins/nginx-vhosts/report deleted file mode 100755 index dcadd716e..000000000 --- a/plugins/nginx-vhosts/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/command-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-nginx-report-single "$@" diff --git a/plugins/nginx-vhosts/report.go b/plugins/nginx-vhosts/report.go new file mode 100644 index 000000000..7434118ff --- /dev/null +++ b/plugins/nginx-vhosts/report.go @@ -0,0 +1,114 @@ +package nginxvhosts + +import ( + "os" + "strconv" + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +type nginxReportProp struct { + name string + appFn common.ReportFunc + computedFn common.ReportFunc + globalFn common.ReportFunc +} + +func wrapGlobal(fn func() string) common.ReportFunc { + return func(string) string { return fn() } +} + +func nginxReportProps() []nginxReportProp { + return []nginxReportProp{ + {"access-log-format", AppAccessLogFormat, ComputedAccessLogFormat, wrapGlobal(GlobalAccessLogFormat)}, + {"access-log-path", AppAccessLogPath, ComputedAccessLogPath, wrapGlobal(GlobalAccessLogPath)}, + {"bind-address-ipv4", AppBindAddressIPv4, ComputedBindAddressIPv4, wrapGlobal(GlobalBindAddressIPv4)}, + {"bind-address-ipv6", AppBindAddressIPv6, ComputedBindAddressIPv6, wrapGlobal(GlobalBindAddressIPv6)}, + {"client-body-timeout", AppClientBodyTimeout, ComputedClientBodyTimeout, wrapGlobal(GlobalClientBodyTimeout)}, + {"client-header-timeout", AppClientHeaderTimeout, ComputedClientHeaderTimeout, wrapGlobal(GlobalClientHeaderTimeout)}, + {"client-max-body-size", AppClientMaxBodySize, ComputedClientMaxBodySize, wrapGlobal(GlobalClientMaxBodySize)}, + {"disable-custom-config", AppDisableCustomConfig, ComputedDisableCustomConfig, wrapGlobal(GlobalDisableCustomConfig)}, + {"error-log-path", AppErrorLogPath, ComputedErrorLogPath, wrapGlobal(GlobalErrorLogPath)}, + {"hsts-include-subdomains", AppHSTSIncludeSubdomains, ComputedHSTSIncludeSubdomains, wrapGlobal(GlobalHSTSIncludeSubdomains)}, + {"hsts-max-age", AppHSTSMaxAge, ComputedHSTSMaxAge, wrapGlobal(GlobalHSTSMaxAge)}, + {"hsts-preload", AppHSTSPreload, ComputedHSTSPreload, wrapGlobal(GlobalHSTSPreload)}, + {"hsts", AppHSTS, ComputedHSTS, wrapGlobal(GlobalHSTS)}, + {"keepalive-timeout", AppKeepaliveTimeout, ComputedKeepaliveTimeout, wrapGlobal(GlobalKeepaliveTimeout)}, + {"lingering-timeout", AppLingeringTimeout, ComputedLingeringTimeout, wrapGlobal(GlobalLingeringTimeout)}, + {"nginx-conf-sigil-path", AppNginxConfSigilPath, ComputedNginxConfSigilPath, wrapGlobal(GlobalNginxConfSigilPath)}, + {"nginx-service-command", AppNginxServiceCommand, ComputedNginxServiceCommand, wrapGlobal(GlobalNginxServiceCommand)}, + {"proxy-buffer-size", AppProxyBufferSize, ComputedProxyBufferSize, wrapGlobal(GlobalProxyBufferSize)}, + {"proxy-buffering", AppProxyBuffering, ComputedProxyBuffering, wrapGlobal(GlobalProxyBuffering)}, + {"proxy-buffers", AppProxyBuffers, ComputedProxyBuffers, wrapGlobal(GlobalProxyBuffers)}, + {"proxy-busy-buffers-size", AppProxyBusyBuffersSize, ComputedProxyBusyBuffersSize, wrapGlobal(GlobalProxyBusyBuffersSize)}, + {"proxy-connect-timeout", AppProxyConnectTimeout, ComputedProxyConnectTimeout, wrapGlobal(GlobalProxyConnectTimeout)}, + {"proxy-keepalive", AppProxyKeepalive, ComputedProxyKeepalive, wrapGlobal(GlobalProxyKeepalive)}, + {"proxy-read-timeout", AppProxyReadTimeout, ComputedProxyReadTimeout, wrapGlobal(GlobalProxyReadTimeout)}, + {"proxy-send-timeout", AppProxySendTimeout, ComputedProxySendTimeout, wrapGlobal(GlobalProxySendTimeout)}, + {"send-timeout", AppSendTimeout, ComputedSendTimeout, wrapGlobal(GlobalSendTimeout)}, + {"underscore-in-headers", AppUnderscoreInHeaders, ComputedUnderscoreInHeaders, wrapGlobal(GlobalUnderscoreInHeaders)}, + {"x-forwarded-for-value", AppXForwardedForValue, ComputedXForwardedForValue, wrapGlobal(GlobalXForwardedForValue)}, + {"x-forwarded-port-value", AppXForwardedPortValue, ComputedXForwardedPortValue, wrapGlobal(GlobalXForwardedPortValue)}, + {"x-forwarded-proto-value", AppXForwardedProtoValue, ComputedXForwardedProtoValue, wrapGlobal(GlobalXForwardedProtoValue)}, + {"x-forwarded-ssl", AppXForwardedSSL, ComputedXForwardedSSL, wrapGlobal(GlobalXForwardedSSL)}, + } +} + +// ReportSingleApp is an internal function that displays the nginx report for one or more apps +func ReportSingleApp(appName string, format string, infoFlag string) error { + if appName != "--global" { + if err := common.VerifyAppName(appName); err != nil { + return err + } + } + isGlobal := appName == "--global" + + flags := map[string]common.ReportFunc{} + for _, p := range nginxReportProps() { + flags["--nginx-"+p.name] = p.appFn + flags["--nginx-computed-"+p.name] = p.computedFn + flags["--nginx-global-"+p.name] = p.globalFn + } + flags["--nginx-last-visited-at"] = reportLastVisitedAt + + if isGlobal { + for key := range flags { + if !strings.Contains(key, "-global-") { + delete(flags, key) + } + } + } + + flagKeys := []string{} + for flagKey := range flags { + flagKeys = append(flagKeys, flagKey) + } + + infoFlags := common.CollectReport(appName, infoFlag, flags) + return common.ReportSingleApp(common.ReportSingleAppInput{ + ReportType: "nginx", + AppName: appName, + InfoFlag: infoFlag, + InfoFlags: infoFlags, + InfoFlagKeys: flagKeys, + Format: format, + TrimPrefix: true, + UppercaseFirstCharacter: true, + EmitLegacyPrefix: false, + }) +} + +func reportLastVisitedAt(appName string) string { + logPath := AppAccessLogPath(appName) + if logPath == "off" || logPath == "/dev/null" { + return "" + } + + info, err := os.Stat(logPath) + if err != nil || !info.Mode().IsRegular() { + return "" + } + + return strconv.FormatInt(info.ModTime().Unix(), 10) +} diff --git a/plugins/nginx-vhosts/src/subcommands/subcommands.go b/plugins/nginx-vhosts/src/subcommands/subcommands.go new file mode 100644 index 000000000..7b2ab9f4d --- /dev/null +++ b/plugins/nginx-vhosts/src/subcommands/subcommands.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + nginxvhosts "github.com/dokku/dokku/plugins/nginx-vhosts" + + flag "github.com/spf13/pflag" +) + +// main entrypoint to all subcommands +func main() { + parts := strings.Split(os.Args[0], "/") + subcommand := parts[len(parts)-1] + + var err error + switch subcommand { + case "report": + args := flag.NewFlagSet("nginx-vhosts:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + reportArgs, flagErr := common.ParseReportArgs("nginx-vhosts", os.Args[2:]) + if flagErr == nil { + args.Parse(reportArgs.OSArgs) + appName := args.Arg(0) + if reportArgs.IsGlobal { + appName = "--global" + } + err = nginxvhosts.CommandReport(appName, *format, reportArgs.InfoFlag) + } + default: + err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/nginx-vhosts/src/triggers/triggers.go b/plugins/nginx-vhosts/src/triggers/triggers.go new file mode 100644 index 000000000..c4174a1ac --- /dev/null +++ b/plugins/nginx-vhosts/src/triggers/triggers.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + nginxvhosts "github.com/dokku/dokku/plugins/nginx-vhosts" +) + +// main entrypoint to all triggers +func main() { + parts := strings.Split(os.Args[0], "/") + trigger := parts[len(parts)-1] + flag.Parse() + + var err error + switch trigger { + case "report": + appName := flag.Arg(0) + err = nginxvhosts.ReportSingleApp(appName, "", "") + default: + err = fmt.Errorf("Invalid plugin trigger call: %s", trigger) + } + + if err != nil { + common.LogFailWithError(err) + } +} diff --git a/plugins/nginx-vhosts/subcommands.go b/plugins/nginx-vhosts/subcommands.go new file mode 100644 index 000000000..fc5439fcf --- /dev/null +++ b/plugins/nginx-vhosts/subcommands.go @@ -0,0 +1,29 @@ +package nginxvhosts + +import ( + "errors" + + "github.com/dokku/dokku/plugins/common" +) + +// CommandReport displays an nginx report for one or more apps +func CommandReport(appName string, format string, infoFlag string) error { + if len(appName) == 0 { + apps, err := common.DokkuApps() + if err != nil { + if errors.Is(err, common.NoAppsExist) { + common.LogWarn(err.Error()) + return nil + } + return err + } + for _, appName := range apps { + if err := ReportSingleApp(appName, format, infoFlag); err != nil { + return err + } + } + return nil + } + + return ReportSingleApp(appName, format, infoFlag) +} diff --git a/plugins/nginx-vhosts/subcommands/report b/plugins/nginx-vhosts/subcommands/report deleted file mode 100755 index 9fcc13b49..000000000 --- a/plugins/nginx-vhosts/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/command-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-nginx-report "$@" From 5d07484cc2a0745ba4636d3ef00a71d0766acb6e Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 7 Jul 2026 07:15:19 -0400 Subject: [PATCH 8/9] refactor: remove unused bash report helper functions With every bash :report subcommand now ported to golang, the shared fn-report-parse-args, fn-report-emit-json, fn-report-filter-global and fn-report-validate-format helpers are no longer referenced and are removed. --- plugins/common/functions | 59 ---------------------------------------- 1 file changed, 59 deletions(-) diff --git a/plugins/common/functions b/plugins/common/functions index 55c6a2ef2..90bac37e8 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -1028,62 +1028,3 @@ fn-registry-docker-config-dir() { echo "$config_dir" fi } - -fn-report-parse-args() { - declare desc="strips --format and --global flags from a report invocation, populating REPORT_FORMAT, REPORT_IS_GLOBAL, REPORT_ARGS" - REPORT_FORMAT="stdout" - REPORT_IS_GLOBAL="false" - REPORT_ARGS=() - while [[ $# -gt 0 ]]; do - case "$1" in - --format) - REPORT_FORMAT="$2" - shift 2 - ;; - --global) - REPORT_IS_GLOBAL="true" - shift - ;; - *) - REPORT_ARGS+=("$1") - shift - ;; - esac - done -} - -fn-report-emit-json() { - declare desc="serializes a flag_map array (passed by name) as a JSON object, stripping the --- prefix from each key" - declare flag_map_name="$1" prefix="$2" - local -n flag_map_ref="$flag_map_name" - local json_output="{}" - for flag in "${flag_map_ref[@]}"; do - local key value - key="$(echo "$flag" | cut -d':' -f1 | sed "s/^--${prefix}-//")" - value="$(echo "$flag" | cut -d':' -f2- | sed 's/^ //')" - json_output="$(echo "$json_output" | jq -c --arg key "$key" --arg value "$value" '. + {($key): $value}')" - done - echo "$json_output" -} - -fn-report-filter-global() { - declare desc="filters a flag_map array (passed by name) in place, keeping only entries with -global- in their flag name" - declare flag_map_name="$1" - local -n flag_map_ref="$flag_map_name" - local filtered=() - for flag in "${flag_map_ref[@]}"; do - local key="$(echo "$flag" | cut -d':' -f1)" - if [[ "$key" == *"-global-"* ]]; then - filtered+=("$flag") - fi - done - flag_map_ref=("${filtered[@]}") -} - -fn-report-validate-format() { - declare desc="rejects --format json when an info flag is also specified" - declare FORMAT="$1" INFO_FLAG="$2" - if [[ "$FORMAT" != "stdout" ]] && [[ -n "$INFO_FLAG" ]]; then - dokku_log_fail "--format flag cannot be specified when specifying an info flag" - fi -} From cdcc10e91c3b51e2585789abdeb88cb314138c99 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 7 Jul 2026 10:45:06 -0400 Subject: [PATCH 9/9] fix: satisfy shfmt lint on certs internal-functions --- plugins/certs/internal-functions | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/certs/internal-functions b/plugins/certs/internal-functions index be7f27578..9d90e1d36 100644 --- a/plugins/certs/internal-functions +++ b/plugins/certs/internal-functions @@ -45,4 +45,3 @@ fn-certs-remove() { plugn trigger post-certs-remove "$APP" plugn trigger post-domains-update "$APP" } -