diff --git a/go.work b/go.work index 98e6dad67..d8b8e65f5 100644 --- a/go.work +++ b/go.work @@ -4,14 +4,27 @@ 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/caddy-vhosts + ./plugins/certs + ./plugins/checks ./plugins/common ./plugins/config ./plugins/cron ./plugins/docker-options + ./plugins/domains + ./plugins/git + ./plugins/haproxy-vhosts ./plugins/logs ./plugins/network ./plugins/nginx-vhosts + ./plugins/openresty-vhosts ./plugins/plugin ./plugins/ports ./plugins/proxy @@ -23,4 +36,5 @@ use ( ./plugins/scheduler-docker-local ./plugins/scheduler-k3s ./plugins/storage + ./plugins/traefik-vhosts ) 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 "$@" 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/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..9d90e1d36 --- a/plugins/certs/internal-functions +++ b/plugins/certs/internal-functions @@ -45,166 +45,3 @@ fn-certs-remove() { plugn trigger post-certs-remove "$APP" 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 "$@" 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/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 -} 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/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 "$@" 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/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 "$@" 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/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 "$@" 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/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" { 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" +}