From 4ae9cca6f17c3a3c00c1f73d1409f88875bc45e6 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Thu, 19 Jun 2025 02:19:08 -0400 Subject: [PATCH] feat: implement app-json-is-valid plugin trigger --- docs/development/plugin-triggers.md | 15 +++++++++ plugins/app-json/appjson.go | 35 ++++++++++++--------- plugins/app-json/triggers.go | 33 ++++++------------- plugins/cron/cron.go | 23 ++++++++++---- plugins/cron/report.go | 2 +- plugins/cron/subcommands.go | 4 +-- plugins/cron/triggers.go | 20 ++++++++++++ plugins/scheduler-docker-local/functions.go | 2 +- plugins/scheduler-k3s/triggers.go | 2 +- 9 files changed, 87 insertions(+), 49 deletions(-) diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index e37e22c90..c790eec4e 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -119,6 +119,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `app-json-is-valid` + +- Description: Checks to see if the provided app.json file is valid +- Invoked by: Appjson extraction during deployment +- Arguments: `$APP $APP_JSON_PATH` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `app-maybe-create` - Description: Creates an app (gated by whether this is globally enabled or not) diff --git a/plugins/app-json/appjson.go b/plugins/app-json/appjson.go index 5ea123f1c..75f0ccced 100644 --- a/plugins/app-json/appjson.go +++ b/plugins/app-json/appjson.go @@ -200,21 +200,9 @@ func GetAppJSON(appName string) (AppJSON, error) { return AppJSON{}, nil } - b, err := os.ReadFile(getProcessSpecificAppJSONPath(appName)) - if err != nil { - return AppJSON{}, fmt.Errorf("Cannot read app.json file: %v", err) - } + appJSONPath := getProcessSpecificAppJSONPath(appName) - if strings.TrimSpace(string(b)) == "" { - return AppJSON{}, nil - } - - var appJSON AppJSON - if err = json.Unmarshal(b, &appJSON); err != nil { - return AppJSON{}, fmt.Errorf("Cannot parse app.json: %v", err) - } - - return appJSON, nil + return ReadAppJSON(appJSONPath) } func GetAutoscalingConfig(appName string, processType string, replicas int) (FormationAutoscaling, bool, error) { @@ -256,3 +244,22 @@ func GetAutoscalingConfig(appName string, processType string, replicas int) (For return autoscaling, true, nil } + +// ReadAppJSON reads an app.json file from a given path +func ReadAppJSON(path string) (AppJSON, error) { + b, err := os.ReadFile(path) + if err != nil { + return AppJSON{}, fmt.Errorf("Cannot read app.json file: %v", err) + } + + if strings.TrimSpace(string(b)) == "" { + return AppJSON{}, nil + } + + var appJSON AppJSON + if err = json.Unmarshal(b, &appJSON); err != nil { + return AppJSON{}, fmt.Errorf("Cannot parse app.json: %v", err) + } + + return appJSON, nil +} diff --git a/plugins/app-json/triggers.go b/plugins/app-json/triggers.go index 92a896e4e..c5d051b5e 100644 --- a/plugins/app-json/triggers.go +++ b/plugins/app-json/triggers.go @@ -1,7 +1,7 @@ package appjson import ( - "encoding/json" + "errors" "fmt" "os" "path" @@ -142,31 +142,16 @@ func TriggerCorePostExtract(appName string, sourceWorkDir string) error { } if hasAppJSON(appName) { - b, err := os.ReadFile(processSpecificAppJSON) + result, err := common.CallPlugnTrigger(common.PlugnTriggerInput{ + Trigger: "app-json-is-valid", + Args: []string{appName, processSpecificAppJSON}, + }) if err != nil { - return err - } - - content := strings.TrimSpace(string(b)) - if content == "" { - return nil - } - - var appJSON AppJSON - if err := json.Unmarshal([]byte(content), &appJSON); err != nil { - return fmt.Errorf("Unable to unmarshal app.json: %v", err.Error()) - } - - if appJSON.Cron != nil { - for i, c := range appJSON.Cron { - if c.Command == "" { - return fmt.Errorf("Missing cron command for app %s (index %d)", appName, i) - } - - if c.Schedule == "" { - return fmt.Errorf("Missing cron schedule for app %s (index %d)", appName, i) - } + if result.StderrContents() != "" { + return errors.New(result.StderrContents()) } + + return err } } diff --git a/plugins/cron/cron.go b/plugins/cron/cron.go index 77e9a6194..5768065a3 100644 --- a/plugins/cron/cron.go +++ b/plugins/cron/cron.go @@ -62,21 +62,32 @@ func (t TemplateCommand) CronCommand() string { return fmt.Sprintf("dokku run --cron-id %s %s %s", t.ID, t.App, t.Command) } +// FetchCronEntriesInput is the input for the FetchCronEntries function +type FetchCronEntriesInput struct { + AppName string + AppJSON *appjson.AppJSON +} + // FetchCronEntries returns a list of cron commands for a given app -func FetchCronEntries(appName string) ([]TemplateCommand, error) { +func FetchCronEntries(input FetchCronEntriesInput) ([]TemplateCommand, error) { + appName := input.AppName commands := []TemplateCommand{} isMaintenance := reportComputedMaintenance(appName) == "true" - appJSON, err := appjson.GetAppJSON(appName) - if err != nil { - return commands, fmt.Errorf("Unable to fetch app.json for app %s: %s", appName, err.Error()) + if input.AppJSON == nil { + appJSON, err := appjson.GetAppJSON(appName) + if err != nil { + return commands, fmt.Errorf("Unable to fetch app.json for app %s: %s", appName, err.Error()) + } + + input.AppJSON = &appJSON } - if appJSON.Cron == nil { + if input.AppJSON.Cron == nil { return commands, nil } - for i, c := range appJSON.Cron { + for i, c := range input.AppJSON.Cron { if c.Command == "" { common.LogWarn(fmt.Sprintf("Missing cron command for app %s (index %d)", appName, i)) continue diff --git a/plugins/cron/report.go b/plugins/cron/report.go index 769ed59a6..3514b8f53 100644 --- a/plugins/cron/report.go +++ b/plugins/cron/report.go @@ -41,7 +41,7 @@ func reportMailto(_ string) string { } func reportTasks(appName string) string { - c, _ := FetchCronEntries(appName) + c, _ := FetchCronEntries(FetchCronEntriesInput{AppName: appName}) return strconv.Itoa(len(c)) } diff --git a/plugins/cron/subcommands.go b/plugins/cron/subcommands.go index aede7d39b..2d5717b4b 100644 --- a/plugins/cron/subcommands.go +++ b/plugins/cron/subcommands.go @@ -26,7 +26,7 @@ func CommandList(appName string, format string) error { return fmt.Errorf("Invalid format specified, supported formats: json, stdout") } - entries, err := FetchCronEntries(appName) + entries, err := FetchCronEntries(FetchCronEntriesInput{AppName: appName}) if err != nil { return err } @@ -79,7 +79,7 @@ func CommandRun(appName string, cronID string, detached bool) error { return err } - entries, err := FetchCronEntries(appName) + entries, err := FetchCronEntries(FetchCronEntriesInput{AppName: appName}) if err != nil { return err } diff --git a/plugins/cron/triggers.go b/plugins/cron/triggers.go index 383a7cd3a..a0f192e74 100644 --- a/plugins/cron/triggers.go +++ b/plugins/cron/triggers.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + appjson "github.com/dokku/dokku/plugins/app-json" "github.com/dokku/dokku/plugins/common" ) @@ -23,6 +24,25 @@ func TriggerCronGetProperty(appName string, key string) error { return nil } +// TriggerAppJSONIsValid validates the cron entries for a given app +func TriggerAppJSONIsValid(appName string, appJSONPath string) error { + if !common.FileExists(appJSONPath) { + return nil + } + + appJSON, err := appjson.ReadAppJSON(appJSONPath) + if err != nil { + return err + } + + _, err = FetchCronEntries(FetchCronEntriesInput{AppName: appName, AppJSON: &appJSON}) + if err != nil { + return err + } + + return nil +} + // TriggerInstall runs the install step for the cron plugin func TriggerInstall() error { if err := common.PropertySetup("cron"); err != nil { diff --git a/plugins/scheduler-docker-local/functions.go b/plugins/scheduler-docker-local/functions.go index 6d4bca180..e11d7c035 100644 --- a/plugins/scheduler-docker-local/functions.go +++ b/plugins/scheduler-docker-local/functions.go @@ -52,7 +52,7 @@ func generateCronEntries() ([]cron.TemplateCommand, error) { return nil } - c, err := cron.FetchCronEntries(appName) + c, err := cron.FetchCronEntries(cron.FetchCronEntriesInput{AppName: appName}) if err != nil { results <- []cron.TemplateCommand{} common.LogWarn(err.Error()) diff --git a/plugins/scheduler-k3s/triggers.go b/plugins/scheduler-k3s/triggers.go index 22fec8b5a..d81094cbe 100644 --- a/plugins/scheduler-k3s/triggers.go +++ b/plugins/scheduler-k3s/triggers.go @@ -266,7 +266,7 @@ func TriggerSchedulerDeploy(scheduler string, appName string, imageTag string) e workingDir := common.GetWorkingDir(appName, image) - allCronEntries, err := cron.FetchCronEntries(appName) + allCronEntries, err := cron.FetchCronEntries(cron.FetchCronEntriesInput{AppName: appName}) if err != nil { return fmt.Errorf("Error fetching cron entries: %w", err) }