feat: implement app-json-is-valid plugin trigger

This commit is contained in:
Jose Diaz-Gonzalez
2025-06-19 02:19:08 -04:00
parent ed851fe399
commit 4ae9cca6f1
9 changed files with 87 additions and 49 deletions

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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))
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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())

View File

@@ -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)
}