Files
dokku/plugins/logs/triggers.go
Jose Diaz-Gonzalez b43929df27 fix: regenerate vector config on app lifecycle changes
The generated vector config is a snapshot of the app list and their sink properties, but it was only ever written by `logs:set` and `logs:vector-start`. Renaming an app left a source filtering on a label no container carries and gave the new name no source at all, so the app kept a sink with nothing feeding it. Cloning produced the same result for the clone, and destroying an app left its source and sink behind, the latter still pointing at an endpoint decommissioned along with the app. The global relabel transform embeds app names directly in generated VRL, so a rename also left behind a branch naming an app that no longer existed. Every case was silent, and the only repair was an operator running `logs:vector-start`. The `post-app-clone-setup`, `post-app-rename-setup` and `post-delete` triggers now rewrite the config, warning rather than failing so that a config write cannot abort the app operation whose state it is derived from.

Closes #8918.
2026-08-09 23:53:12 -04:00

171 lines
4.3 KiB
Go

package logs
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/dokku/dokku/plugins/common"
dockeroptions "github.com/dokku/dokku/plugins/docker-options"
)
// TriggerDockerArgsProcessDeploy outputs the logs plugin docker options for an app
func TriggerDockerArgsProcessDeploy(appName string) error {
stdin, err := io.ReadAll(os.Stdin)
if err != nil {
return err
}
allowedDrivers := map[string]bool{
"local": true,
"json-file": true,
}
ignoreMaxSize := false
options, err := dockeroptions.GetDockerOptionsForPhase(appName, "deploy")
if err != nil {
return err
}
hasDriverOpt := false
for _, option := range options {
if !strings.HasPrefix(option, "--log-driver=") {
continue
}
hasDriverOpt = true
logDriver := strings.TrimPrefix(option, "--log-driver=")
if !allowedDrivers[logDriver] {
ignoreMaxSize = true
}
break
}
if !hasDriverOpt {
result, _ := common.CallExecCommand(common.ExecCommandInput{
Command: common.DockerBin(),
Args: []string{"system", "info", "--format", "{{ .LoggingDriver }}"},
})
if !allowedDrivers[result.StdoutContents()] {
ignoreMaxSize = true
}
}
if !ignoreMaxSize {
maxSize := common.PropertyGet("logs", appName, "max-size")
if maxSize == "" {
maxSize = common.PropertyGetDefault("logs", "--global", "max-size", MaxSize)
}
if maxSize != "unlimited" {
fmt.Printf(" --log-opt=max-size=%s ", maxSize)
}
}
fmt.Print(string(stdin))
return nil
}
// TriggerInstall initializes log configuration
func TriggerInstall() error {
if err := common.PropertySetup("logs"); err != nil {
return fmt.Errorf("Unable to install the logs plugin: %s", err.Error())
}
if err := common.SetupAppData("logs"); err != nil {
return err
}
logDirectory := filepath.Join(common.MustGetEnv("DOKKU_LOGS_DIR"), "apps")
if err := os.MkdirAll(logDirectory, 0755); err != nil {
return err
}
return common.SetPermissions(common.SetPermissionInput{
Filename: logDirectory,
Mode: os.FileMode(0755),
})
}
// TriggerLogsGetProperty writes the logs key to stdout for a given app container
func TriggerLogsGetProperty(appName string, key string) error {
if key != "max-size" {
return errors.New("Invalid logs property specified")
}
value := common.PropertyGet("logs", appName, "max-size")
if value == "" {
value = common.PropertyGetDefault("logs", "--global", "max-size", MaxSize)
}
fmt.Println(value)
return nil
}
// TriggerPostAppCloneSetup creates new logs files and regenerates the vector
// config so that the clone gets a source of its own for the sink it inherited
func TriggerPostAppCloneSetup(oldAppName string, newAppName string) error {
err := common.PropertyClone("logs", oldAppName, newAppName)
if err != nil {
return err
}
if err := common.CloneAppData("logs", oldAppName, newAppName); err != nil {
return err
}
regenerateVectorConfig()
return nil
}
// TriggerPostAppRename removes the old app data
func TriggerPostAppRename(oldAppName string, newAppName string) error {
return common.MigrateAppDataDirectory("logs", oldAppName, newAppName)
}
// TriggerPostAppRenameSetup renames logs files and regenerates the vector config.
// Both app roots exist at this point, but the old app no longer has any logs
// properties, so it drops out of the generated config.
func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error {
if err := common.PropertyClone("logs", oldAppName, newAppName); err != nil {
return err
}
if err := common.PropertyDestroy("logs", oldAppName); err != nil {
return err
}
if err := common.CloneAppData("logs", oldAppName, newAppName); err != nil {
return err
}
regenerateVectorConfig()
return nil
}
// TriggerPostCreate ensures apps have the correct data directory structure
func TriggerPostCreate(appName string) error {
return common.CreateAppDataDirectory("logs", appName)
}
// TriggerPostDelete destroys the logs property for a given app container and
// regenerates the vector config. The regeneration runs after the properties are
// destroyed so that the app drops out of the config even though its app root is
// only removed later in the destroy flow.
func TriggerPostDelete(appName string) error {
dataErr := common.RemoveAppDataDirectory("logs", appName)
propertyErr := common.PropertyDestroy("logs", appName)
regenerateVectorConfig()
if dataErr != nil {
return dataErr
}
return propertyErr
}