package appjson import ( "errors" "fmt" "os" "strings" "github.com/dokku/dokku/plugins/common" ) // TriggerAppJSONProcessDeployParallelism returns the max number of processes to deploy in parallel func TriggerAppJSONProcessDeployParallelism(appName string, processType string) error { appJSON, err := GetAppJSON(appName) if err != nil { return err } parallelism := 1 for procType, formation := range appJSON.Formation { if procType != processType { continue } if formation.MaxParallel == nil { continue } if *formation.MaxParallel > 0 { parallelism = *formation.MaxParallel } } fmt.Println(parallelism) return nil } // TriggerAppJSONGetContent outputs the contents of the app-json file, if any func TriggerAppJSONGetContent(appName string) error { if !hasAppJSON(appName) { fmt.Print("{}") return nil } b, err := os.ReadFile(getProcessSpecificAppJSONPath(appName)) if err != nil { return fmt.Errorf("Cannot read app.json file: %v", err) } content := strings.TrimSpace(string(b)) if content == "" { fmt.Print("{}") return nil } fmt.Print(content) return nil } // TriggerCorePostDeploy moves the extracted app.json to the app data directory // allowing the app to be restored on boot func TriggerCorePostDeploy(appName string) error { return common.CorePostDeploy(common.CorePostDeployInput{ AppName: appName, Destination: common.GetAppDataDirectory("app-json", appName), PluginName: "app-json", ExtractedPaths: []common.CorePostDeployPath{ {Path: "app.json", IsDirectory: false}, }, }) } // TriggerCorePostExtract ensures that the main app.json is the one specified by app-json-path func TriggerCorePostExtract(appName string, sourceWorkDir string) error { destination := common.GetAppDataDirectory("app-json", appName) appJSONPath := strings.Trim(reportComputedAppjsonpath(appName), "/") if appJSONPath == "" { appJSONPath = "app.json" } validator := func(appName string, path string) error { if !common.FileExists(path) { return nil } result, err := common.CallPlugnTrigger(common.PlugnTriggerInput{ Trigger: "app-json-is-valid", Args: []string{appName, path}, StreamStdout: true, StreamStderr: true, }) if err != nil { if result.StderrContents() != "" { return errors.New(result.StderrContents()) } return err } return nil } results, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{ Trigger: "builder-get-property", Args: []string{appName, "build-dir"}, }) buildDir := results.StdoutContents() return common.CorePostExtract(common.CorePostExtractInput{ AppName: appName, BuildDir: buildDir, Destination: destination, PluginName: "app-json", SourceWorkDir: sourceWorkDir, ToExtract: []common.CorePostExtractToExtract{ { Path: appJSONPath, IsDirectory: false, Name: "app.json", Destination: "app.json", Validator: validator, }, }, }) } // TriggerInstall initializes app-json directory structures func TriggerInstall() error { if err := common.PropertySetup("app-json"); err != nil { return fmt.Errorf("Unable to install the app-json plugin: %s", err.Error()) } if err := common.SetupAppData("app-json"); err != nil { return err } return nil } // TriggerPostAppCloneSetup creates new app-json files func TriggerPostAppCloneSetup(oldAppName string, newAppName string) error { err := common.PropertyClone("app-json", oldAppName, newAppName) if err != nil { return err } return common.CloneAppData("app-json", oldAppName, newAppName) } // TriggerPostAppRename removes the old app data func TriggerPostAppRename(oldAppName string, newAppName string) error { return common.MigrateAppDataDirectory("app-json", oldAppName, newAppName) } // TriggerPostAppRenameSetup renames app-json files func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error { if err := common.PropertyClone("app-json", oldAppName, newAppName); err != nil { return err } if err := common.PropertyDestroy("app-json", oldAppName); err != nil { return err } return common.CloneAppData("app-json", oldAppName, newAppName) } // TriggerPostCreate ensures apps have the correct data directory structure func TriggerPostCreate(appName string) error { return common.CreateAppDataDirectory("app-json", appName) } // TriggerPostDelete destroys the app-json data for a given app container func TriggerPostDelete(appName string) error { dataErr := common.RemoveAppDataDirectory("app-json", appName) propertyErr := common.PropertyDestroy("app-json", appName) if dataErr != nil { return dataErr } return propertyErr } // TriggerPostDeploy is a trigger to execute the postdeploy deployment task func TriggerPostDeploy(appName string, imageTag string) error { image, err := common.GetDeployingAppImageName(appName, imageTag, "") if err != nil { return err } return executeScript(appName, image, imageTag, "postdeploy") } func TriggerPreReleaseBuilder(builderType string, appName string, image string) error { parts := strings.Split(image, ":") imageTag := parts[len(parts)-1] return executeScript(appName, image, imageTag, "predeploy") } // TriggerPostReleaseBuilder is a trigger to execute predeploy and release deployment tasks func TriggerPostReleaseBuilder(builderType string, appName string, image string) error { parts := strings.Split(image, ":") imageTag := parts[len(parts)-1] if err := executeScript(appName, image, imageTag, "release"); err != nil { return err } if err := setScale(appName); err != nil { return err } if common.PropertyGet("common", appName, "deployed") == "true" { return nil } // Ensure that a failed postdeploy does not trigger twice if common.PropertyGet("app-json", appName, "heroku.postdeploy") == "executed" { return nil } if err := common.PropertyWrite("app-json", appName, "heroku.postdeploy", "executed"); err != nil { return err } return executeScript(appName, image, imageTag, "heroku.postdeploy") }