Adds typed JSON build records under data/builds/<app>/<build-id>.{json,log} keyed on a stable base36 ULID-style DOKKU_BUILD_ID generated for every deploy. The new commands surface that history (builds:list, builds:info, builds:prune) and an operator-configurable retention via builds:set retention. The existing builds:cancel and builds:output now key on the build-id (with safe handling for already-finalized and abandoned records), and the per-build log file replaces journalctl as the durable source of truth for builds:output.
212 lines
5.2 KiB
Go
212 lines
5.2 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
|
|
"github.com/dokku/dokku/plugins/common"
|
|
)
|
|
|
|
// Get retrieves a value from a config. If appName is empty the global config is used.
|
|
func Get(appName string, key string) (value string, ok bool) {
|
|
env, err := loadAppOrGlobalEnv(appName)
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
if err = validateKey(key); err != nil {
|
|
return "", false
|
|
}
|
|
return env.Get(key)
|
|
}
|
|
|
|
// GetWithDefault gets a value from a config. If appName is empty the global config is used. If the appName or key do not exist defaultValue is returned.
|
|
func GetWithDefault(appName string, key string, defaultValue string) (value string) {
|
|
value, ok := Get(appName, key)
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
return value
|
|
}
|
|
|
|
// SetMany variables in the environment. If appName is empty the global config is used. If restart is true the app is restarted.
|
|
func SetMany(appName string, entries map[string]string, replace bool, restart bool) (err error) {
|
|
global := appName == "" || appName == "--global"
|
|
env, err := loadAppOrGlobalEnv(appName)
|
|
if err != nil {
|
|
return
|
|
}
|
|
keys := make([]string, 0, len(entries))
|
|
for k := range entries {
|
|
if err = validateKey(k); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
if replace {
|
|
env.Clear()
|
|
}
|
|
for k, v := range entries {
|
|
env.Set(k, v)
|
|
keys = append(keys, k)
|
|
}
|
|
if len(entries) != 0 {
|
|
common.LogInfo1Quiet("Setting config vars")
|
|
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
|
|
fmt.Println(prettyPrintEnvEntries(" ", entries))
|
|
}
|
|
env.Write()
|
|
common.SetPermissions(common.SetPermissionInput{
|
|
Filename: env.Filename(),
|
|
Mode: os.FileMode(0600),
|
|
})
|
|
triggerUpdate(appName, "set", keys)
|
|
}
|
|
if !global && restart && shouldRestart(appName) {
|
|
triggerRestart(appName)
|
|
}
|
|
return
|
|
}
|
|
|
|
// UnsetMany a value in a config. If appName is empty the global config is used. If restart is true the app is restarted.
|
|
func UnsetMany(appName string, keys []string, restart bool) (err error) {
|
|
global := appName == "" || appName == "--global"
|
|
env, err := loadAppOrGlobalEnv(appName)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var changed = false
|
|
for _, k := range keys {
|
|
if err = validateKey(k); err != nil {
|
|
return
|
|
}
|
|
}
|
|
for _, k := range keys {
|
|
if _, hasKey := env.Map()[k]; hasKey {
|
|
common.LogInfo1Quiet(fmt.Sprintf("Unsetting %s", k))
|
|
env.Unset(k)
|
|
changed = true
|
|
} else {
|
|
common.LogInfo1Quiet(fmt.Sprintf("Skipping %s, it is not set in the environment", k))
|
|
}
|
|
}
|
|
if changed {
|
|
env.Write()
|
|
common.SetPermissions(common.SetPermissionInput{
|
|
Filename: env.Filename(),
|
|
Mode: os.FileMode(0600),
|
|
})
|
|
triggerUpdate(appName, "unset", keys)
|
|
}
|
|
if !global && restart && shouldRestart(appName) {
|
|
triggerRestart(appName)
|
|
}
|
|
return
|
|
}
|
|
|
|
// UnsetAll removes all config keys
|
|
func UnsetAll(appName string, restart bool) (err error) {
|
|
global := appName == "" || appName == "--global"
|
|
env, err := loadAppOrGlobalEnv(appName)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var changed = false
|
|
for k := range env.Map() {
|
|
common.LogInfo1Quiet(fmt.Sprintf("Unsetting %s", k))
|
|
env.Unset(k)
|
|
changed = true
|
|
}
|
|
if changed {
|
|
env.Write()
|
|
common.SetPermissions(common.SetPermissionInput{
|
|
Filename: env.Filename(),
|
|
Mode: os.FileMode(0600),
|
|
})
|
|
triggerUpdate(appName, "clear", []string{})
|
|
}
|
|
if !global && restart && shouldRestart(appName) {
|
|
triggerRestart(appName)
|
|
}
|
|
return
|
|
}
|
|
|
|
func shouldRestart(appName string) bool {
|
|
results, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{
|
|
Trigger: "ps-get-property",
|
|
Args: []string{appName, "restore"},
|
|
})
|
|
return results.StdoutContents() != "false"
|
|
}
|
|
|
|
func triggerRestart(appName string) {
|
|
if !common.IsDeployed(appName) {
|
|
return
|
|
}
|
|
|
|
common.LogInfo1(fmt.Sprintf("Restarting app %s", appName))
|
|
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
|
|
Trigger: "release-and-deploy",
|
|
Args: []string{appName, "", "config-redeploy"},
|
|
StreamStdio: true,
|
|
})
|
|
if err != nil {
|
|
common.LogWarn(fmt.Sprintf("Failure while restarting app: %s", err))
|
|
}
|
|
}
|
|
|
|
func triggerUpdate(appName string, operation string, args []string) {
|
|
args = append([]string{appName, operation}, args...)
|
|
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
|
|
Trigger: "post-config-update",
|
|
Args: args,
|
|
StreamStdio: true,
|
|
})
|
|
if err != nil {
|
|
common.LogWarn(fmt.Sprintf("Failure while triggering post-config-update: %s", err))
|
|
}
|
|
}
|
|
|
|
// getEnvironment for the given app (global config if appName is empty). Merge with global environment if merged is true.
|
|
func getEnvironment(appName string, merged bool) (env *Env) {
|
|
var err error
|
|
if appName != "" && merged {
|
|
env, err = LoadMergedAppEnv(appName)
|
|
} else {
|
|
env, err = loadAppOrGlobalEnv(appName)
|
|
}
|
|
|
|
if err != nil {
|
|
common.LogFailWithError(err)
|
|
}
|
|
|
|
return env
|
|
}
|
|
|
|
func getAppNameOrGlobal(appName string, global bool) (string, error) {
|
|
if global {
|
|
return appName, nil
|
|
}
|
|
|
|
if err := common.VerifyAppName(appName); err != nil {
|
|
return appName, err
|
|
}
|
|
|
|
return appName, nil
|
|
}
|
|
|
|
func loadAppOrGlobalEnv(appName string) (env *Env, err error) {
|
|
if appName == "" || appName == "--global" {
|
|
return LoadGlobalEnv()
|
|
}
|
|
return LoadAppEnv(appName)
|
|
}
|
|
|
|
func validateKey(key string) error {
|
|
r, _ := regexp.Compile("^[a-zA-Z_][a-zA-Z0-9_]*$")
|
|
if !r.MatchString(key) {
|
|
return fmt.Errorf("Invalid key name: '%s'", key)
|
|
}
|
|
return nil
|
|
}
|