diff --git a/docs/appendices/0.37.0-migration-guide.md b/docs/appendices/0.37.0-migration-guide.md index 6aa474994..ac01fa70f 100644 --- a/docs/appendices/0.37.0-migration-guide.md +++ b/docs/appendices/0.37.0-migration-guide.md @@ -1,5 +1,9 @@ # 0.36.0 Migration Guide +## Changes + +- The `app.json` file format is now parsed as JSONC and supports inline comments. + ## Removals - Support for the `DOKKU_DOCKERFILE_CACHE_BUILD` environment variable has been removed. Use the `docker-options` plugin to set build arguments for your app instead. diff --git a/plugins/app-json/appjson.go b/plugins/app-json/appjson.go index fe59c9942..c071dc908 100644 --- a/plugins/app-json/appjson.go +++ b/plugins/app-json/appjson.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/dokku/dokku/plugins/common" + "github.com/tailscale/hujson" "k8s.io/utils/ptr" ) @@ -262,8 +263,14 @@ func ReadAppJSON(path string) (AppJSON, error) { return AppJSON{}, nil } + ast, err := hujson.Parse(b) + if err != nil { + return AppJSON{}, fmt.Errorf("Cannot parse app.json as jsonc: %v", err) + } + ast.Standardize() + var appJSON AppJSON - if err = json.Unmarshal(b, &appJSON); err != nil { + if err = json.Unmarshal(ast.Pack(), &appJSON); err != nil { return AppJSON{}, fmt.Errorf("Cannot parse app.json: %v", err) } diff --git a/plugins/app-json/functions.go b/plugins/app-json/functions.go index b596044c7..55111a5de 100644 --- a/plugins/app-json/functions.go +++ b/plugins/app-json/functions.go @@ -133,11 +133,7 @@ func cleanupDeploymentContainer(containerID string, phase string) error { } func executeScript(appName string, image string, imageTag string, phase string) error { - phaseName := phase - if phase == "heroku.postdeploy" { - phaseName = "first deploy postdeploy" - } - common.LogInfo1(fmt.Sprintf("Checking for %s task", phaseName)) + common.LogInfo1(fmt.Sprintf("Checking for %s task", phase)) command := "" phaseSource := "" if phase == "release" { @@ -152,14 +148,14 @@ func executeScript(appName string, image string, imageTag string, phase string) } if command == "" { - common.LogVerbose(fmt.Sprintf("No %s task found, skipping", phaseName)) + common.LogVerbose(fmt.Sprintf("No %s task found, skipping", phase)) return nil } if phase == "predeploy" { - common.LogVerbose(fmt.Sprintf("Executing %s task from %s: %s", phaseName, phaseSource, command)) + common.LogVerbose(fmt.Sprintf("Executing %s task from %s: %s", phase, phaseSource, command)) } else { - common.LogVerbose(fmt.Sprintf("Executing %s task from %s in ephemeral container: %s", phaseName, phaseSource, command)) + common.LogVerbose(fmt.Sprintf("Executing %s task from %s in ephemeral container: %s", phase, phaseSource, command)) } isHerokuishImage := common.IsImageHerokuishBased(image, appName) @@ -271,15 +267,15 @@ func executeScript(appName string, image string, imageTag string, phase string) defer cleanupDeploymentContainer(containerID, phase) if !waitForExecution(containerID) { - common.LogInfo2Quiet(fmt.Sprintf("Start of %s %s task (%s) output", appName, phaseName, containerID[0:9])) + common.LogInfo2Quiet(fmt.Sprintf("Start of %s %s task (%s) output", appName, phase, containerID[0:9])) common.LogVerboseQuietContainerLogs(containerID) - common.LogInfo2Quiet(fmt.Sprintf("End of %s %s task (%s) output", appName, phaseName, containerID[0:9])) - return fmt.Errorf("Execution of %s task failed: %s", phaseName, command) + common.LogInfo2Quiet(fmt.Sprintf("End of %s %s task (%s) output", appName, phase, containerID[0:9])) + return fmt.Errorf("Execution of %s task failed: %s", phase, command) } - common.LogInfo2Quiet(fmt.Sprintf("Start of %s %s task (%s) output", appName, phaseName, containerID[0:9])) + common.LogInfo2Quiet(fmt.Sprintf("Start of %s %s task (%s) output", appName, phase, containerID[0:9])) common.LogVerboseQuietContainerLogs(containerID) - common.LogInfo2Quiet(fmt.Sprintf("End of %s %s task (%s) output", appName, phaseName, containerID[0:9])) + common.LogInfo2Quiet(fmt.Sprintf("End of %s %s task (%s) output", appName, phase, containerID[0:9])) if phase != "predeploy" { return nil diff --git a/plugins/app-json/go.mod b/plugins/app-json/go.mod index 61ab3d8d0..dea24b213 100644 --- a/plugins/app-json/go.mod +++ b/plugins/app-json/go.mod @@ -6,6 +6,7 @@ require ( github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/spf13/pflag v1.0.10 + github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a k8s.io/utils v0.0.0-20240102154912-e7106e64919e ) @@ -23,6 +24,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pkg/sftp v1.13.5 // indirect github.com/ryanuber/columnize v2.1.2+incompatible // indirect + github.com/stretchr/testify v1.8.4 // indirect golang.org/x/crypto v0.43.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect diff --git a/plugins/app-json/go.sum b/plugins/app-json/go.sum index 7276659c5..d78d4f859 100644 --- a/plugins/app-json/go.sum +++ b/plugins/app-json/go.sum @@ -39,8 +39,11 @@ github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a h1:a6TNDN9CgG+cYjaeN8l2mc4kSz2iMiCDQxPEyltUV/I= +github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= diff --git a/plugins/app-json/triggers.go b/plugins/app-json/triggers.go index 06b5126e0..7f23273e9 100644 --- a/plugins/app-json/triggers.go +++ b/plugins/app-json/triggers.go @@ -1,9 +1,9 @@ package appjson import ( + "encoding/json" "errors" "fmt" - "os" "strings" "github.com/dokku/dokku/plugins/common" @@ -42,18 +42,17 @@ func TriggerAppJSONGetContent(appName string) error { return nil } - b, err := os.ReadFile(getProcessSpecificAppJSONPath(appName)) + appJSON, err := ReadAppJSON(getProcessSpecificAppJSONPath(appName)) if err != nil { - return fmt.Errorf("Cannot read app.json file: %v", err) + return err } - content := strings.TrimSpace(string(b)) - if content == "" { - fmt.Print("{}") - return nil + content, err := json.Marshal(appJSON) + if err != nil { + return err } - fmt.Print(content) + fmt.Print(string(content)) return nil } diff --git a/plugins/cron/go.mod b/plugins/cron/go.mod index bc5330303..ec154b9e2 100644 --- a/plugins/cron/go.mod +++ b/plugins/cron/go.mod @@ -26,6 +26,7 @@ require ( github.com/otiai10/mint v1.6.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/sftp v1.13.5 // indirect + github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a // indirect golang.org/x/crypto v0.43.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect diff --git a/plugins/cron/go.sum b/plugins/cron/go.sum index 83cad516d..b6ad7c9eb 100644 --- a/plugins/cron/go.sum +++ b/plugins/cron/go.sum @@ -51,8 +51,11 @@ github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a h1:a6TNDN9CgG+cYjaeN8l2mc4kSz2iMiCDQxPEyltUV/I= +github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= diff --git a/plugins/scheduler-docker-local/go.mod b/plugins/scheduler-docker-local/go.mod index 5fc84d9f3..7a1ae9bc3 100644 --- a/plugins/scheduler-docker-local/go.mod +++ b/plugins/scheduler-docker-local/go.mod @@ -26,7 +26,7 @@ require ( github.com/pkg/sftp v1.13.5 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/ryanuber/columnize v2.1.2+incompatible // indirect - github.com/stretchr/testify v1.8.4 // indirect + github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a // indirect golang.org/x/crypto v0.43.0 // indirect golang.org/x/sys v0.37.0 // indirect k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect diff --git a/plugins/scheduler-docker-local/go.sum b/plugins/scheduler-docker-local/go.sum index 7e63a2e6c..bf0d48337 100644 --- a/plugins/scheduler-docker-local/go.sum +++ b/plugins/scheduler-docker-local/go.sum @@ -52,6 +52,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a h1:a6TNDN9CgG+cYjaeN8l2mc4kSz2iMiCDQxPEyltUV/I= +github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= diff --git a/plugins/scheduler-k3s/go.mod b/plugins/scheduler-k3s/go.mod index 79ce1f627..1fd43a278 100644 --- a/plugins/scheduler-k3s/go.mod +++ b/plugins/scheduler-k3s/go.mod @@ -139,6 +139,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/cobra v1.10.1 // indirect + github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a // indirect github.com/traefik/paerser v0.2.2 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect diff --git a/plugins/scheduler-k3s/go.sum b/plugins/scheduler-k3s/go.sum index baa72c657..c1a24645d 100644 --- a/plugins/scheduler-k3s/go.sum +++ b/plugins/scheduler-k3s/go.sum @@ -341,6 +341,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a h1:a6TNDN9CgG+cYjaeN8l2mc4kSz2iMiCDQxPEyltUV/I= +github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo= github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ= github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24= github.com/traefik/traefik/v2 v2.11.30 h1:Jkb5H8aI9lKQY/DDqwK3JP7iNUEnRfNLWXsr3azFf8w= diff --git a/tests/apps/python/app-heroku-postdeploy.json b/tests/apps/python/app-heroku-postdeploy.json new file mode 100644 index 000000000..0daee49b0 --- /dev/null +++ b/tests/apps/python/app-heroku-postdeploy.json @@ -0,0 +1,24 @@ +{ + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "python/http.server", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + }, + "scripts": { + "dokku": { + // this is a comment for the postdeploy script + "postdeploy": "touch /app/postdeploy.test", + // this is a comment for the predeploy script + "predeploy": "touch /app/predeploy.test" + }, + "postdeploy": "touch /app/heroku-postdeploy.test" + } +} \ No newline at end of file diff --git a/tests/apps/python/app.json b/tests/apps/python/app.json index 698956d99..246998f6f 100644 --- a/tests/apps/python/app.json +++ b/tests/apps/python/app.json @@ -14,7 +14,9 @@ }, "scripts": { "dokku": { + // this is a comment for the postdeploy script "postdeploy": "touch /app/postdeploy.test", + // this is a comment for the predeploy script "predeploy": "touch /app/predeploy.test" } } diff --git a/tests/unit/app-json-4.bats b/tests/unit/app-json-4.bats index 283d88034..315d1c777 100644 --- a/tests/unit/app-json-4.bats +++ b/tests/unit/app-json-4.bats @@ -50,7 +50,7 @@ teardown() { run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_failing_postdeploy echo "output: $output" echo "status: $status" - assert_output_contains "Execution of first deploy postdeploy task failed" + assert_output_contains "Execution of heroku.postdeploy task failed" assert_failure } diff --git a/tests/unit/app-json.bats b/tests/unit/app-json.bats index ba8325f6a..4c899d979 100644 --- a/tests/unit/app-json.bats +++ b/tests/unit/app-json.bats @@ -47,7 +47,12 @@ teardown() { } @test "(app-json) app.json scripts postdeploy" { - run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_postdeploy_command + run /bin/bash -c "dokku app-json:set $TEST_APP appjson-path app-heroku-postdeploy.json" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index 06546ce70..37e71d38a 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -561,15 +561,6 @@ add_release_command() { echo "release: touch /app/release.test" >>"$APP_REPO_DIR/Procfile" } -add_postdeploy_command() { - local APP="$1" - local APP_REPO_DIR="$2" - [[ -z "$APP" ]] && local APP="$TEST_APP" - touch "$APP_REPO_DIR/app.json" - contents=$(jq '.scripts.postdeploy = "touch /app/heroku-postdeploy.test"' "$APP_REPO_DIR/app.json") - echo "${contents}" >"$APP_REPO_DIR/app.json" -} - move_dockerfile_into_place() { local APP="$1" local APP_REPO_DIR="$2"