Files
dokku/plugins/ps/subcommands.go
Jose Diaz-Gonzalez 857d115fdd feat: add --format json support to ps:scale
The `ps:scale` command now accepts a `--format` flag that defaults to `stdout` and can be set to `json` to emit the current formation as a JSON array of process type and quantity objects, matching the JSON output the `:report` subcommands already provide. The flag only applies when displaying the current formation; it is ignored when process types are supplied for scaling. When no scale has been set for the app, the JSON output is an empty array.
2026-07-06 13:57:19 -04:00

219 lines
5.4 KiB
Go

package ps
import (
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/gofrs/flock"
)
// CommandInspect displays a sanitized version of docker inspect for an app
func CommandInspect(appName string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
scheduler := common.GetAppScheduler(appName)
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "scheduler-inspect",
Args: []string{scheduler, appName},
StreamStdio: true,
})
return err
}
// CommandRebuild rebuilds an app from source
func CommandRebuild(appName string, allApps bool, parallelCount int) error {
if allApps {
return common.RunCommandAgainstAllApps(Rebuild, "rebuild", parallelCount)
}
if err := common.VerifyAppName(appName); err != nil {
return err
}
return Rebuild(appName)
}
// CommandReport displays a ps 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)
}
// CommandRestart restarts an app
func CommandRestart(appName string, processName string, allApps bool, parallelCount int) error {
if allApps {
if processName != "" {
return errors.New("Unable to restart all apps when specifying a process name")
}
return common.RunCommandAgainstAllApps(Restart, "restart", parallelCount)
}
if err := common.VerifyAppName(appName); err != nil {
return err
}
if processName != "" {
return RestartProcess(appName, processName)
}
return Restart(appName)
}
// CommandRestore starts previously running apps e.g. after reboot
func CommandRestore(appName string, allApps bool, parallelCount int) error {
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "pre-restore",
StreamStdio: true,
})
if err != nil {
return fmt.Errorf("Error running pre-restore: %s", err)
}
if allApps {
if err := restorePrep(); err != nil {
return err
}
return common.RunCommandAgainstAllApps(Restore, "restore", parallelCount)
}
if appName == "" {
common.LogWarn("Restore specified without app, assuming --all")
if err := restorePrep(); err != nil {
return err
}
return common.RunCommandAgainstAllApps(Restore, "restore", parallelCount)
}
if err := common.VerifyAppName(appName); err != nil {
return err
}
return Restore(appName)
}
// CommandRetire ensures old containers are retired
func CommandRetire(appName string) error {
lockFile := filepath.Join(common.GetDataDirectory("ps"), "retire")
scheduler := ""
if appName == "" {
scheduler = common.GetGlobalScheduler()
} else {
scheduler = common.GetAppScheduler(appName)
}
fileLock := flock.New(lockFile)
locked, err := fileLock.TryLock()
if err != nil {
return &RetireLockFailed{&err}
}
defer fileLock.Unlock()
if !locked {
return &RetireLockFailed{}
}
common.LogInfo1("Retiring old containers and images")
_, err = common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "scheduler-retire",
Args: []string{scheduler, appName},
StreamStdio: true,
})
if err != nil {
return fmt.Errorf("Error retiring containers: %w", err)
}
common.LogInfo1("Retiring expired run containers")
_, err = common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "scheduler-run-retire",
StreamStdio: true,
})
if err != nil {
return fmt.Errorf("Error retiring expired run containers: %w", err)
}
return err
}
// CommandScale gets or sets how many instances of a given process to run
func CommandScale(appName string, skipDeploy bool, format string, processTuples []string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
if len(processTuples) == 0 {
return scaleReport(appName, format)
}
if !canScaleApp(appName) {
return fmt.Errorf("App %s contains an app.json file with a formations key and cannot be manually scaled", appName)
}
common.LogInfo1(fmt.Sprintf("Scaling %s processes: %s", appName, strings.Join(processTuples, " ")))
return scaleSet(scaleSetInput{
appName: appName,
skipDeploy: skipDeploy,
clearExisting: false,
processTuples: processTuples,
deployOnlyChanged: true,
})
}
// CommandSet sets or clears a ps property for an app
func CommandSet(appName string, property string, value string) error {
if property == "restart-policy" && value != "" && !isValidRestartPolicy(value) {
return errors.New("Invalid restart-policy specified")
}
common.CommandPropertySet("ps", appName, property, value, DefaultProperties, GlobalProperties)
return nil
}
// CommandStart starts an app
func CommandStart(appName string, allApps bool, parallelCount int) error {
if allApps {
return common.RunCommandAgainstAllApps(Start, "start", parallelCount)
}
if err := common.VerifyAppName(appName); err != nil {
return err
}
return Start(appName)
}
// CommandStop stops an app
func CommandStop(appName string, allApps bool, parallelCount int) error {
if allApps {
return common.RunCommandAgainstAllApps(Stop, "stop", parallelCount)
}
if err := common.VerifyAppName(appName); err != nil {
return err
}
return Stop(appName)
}