Files
dokku/plugins/logs/functions.go
Jose Diaz-Gonzalez 6ce10b5be6 fix: ensure compose projects are spawned from the /tmp directory
A recent update to compose executes a stat call in the current working directory, which may have incorrect permissions for execution once the user is changed to the dokku user. This change forces all compose commands to execute in the /tmp directory by using a helper function to execute compose up/down.

Closes #7705
2025-05-24 23:31:41 -04:00

247 lines
6.2 KiB
Go

package logs
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"html/template"
"os"
"path/filepath"
"strings"
"github.com/dokku/dokku/plugins/common"
)
type vectorConfig struct {
Sources map[string]vectorSource `json:"sources"`
Sinks map[string]VectorSink `json:"sinks"`
}
type vectorSource struct {
Type string `json:"type"`
IncludeLabels []string `json:"include_labels,omitempty"`
}
type vectorTemplateData struct {
DokkuLibRoot string
DokkuLogsDir string
VectorImage string
}
const vectorContainerName = "vector-vector-1"
const vectorOldContainerName = "vector"
func getComposeFile() ([]byte, error) {
result, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "vector-template-source",
})
if err == nil && result.ExitCode == 0 && strings.TrimSpace(result.Stdout) != "" {
contents, err := os.ReadFile(strings.TrimSpace(result.Stdout))
if err != nil {
return []byte{}, fmt.Errorf("Unable to read compose template: %s", err)
}
return contents, nil
}
contents, err := templates.ReadFile("templates/compose.yml.tmpl")
if err != nil {
return []byte{}, fmt.Errorf("Unable to read compose template: %s", err)
}
return contents, nil
}
func startVectorContainer(vectorImage string) error {
if !common.IsComposeInstalled() {
return errors.New("Required docker compose plugin is not installed")
}
if common.ContainerExists(vectorOldContainerName) {
return errors.New("Vector container %s already exists in old format, run 'dokku logs:vector-stop' once to remove it")
}
tmpFile, err := os.CreateTemp(os.TempDir(), "vector-compose-*.yml")
if err != nil {
return fmt.Errorf("Unable to create temporary file: %s", err)
}
defer os.Remove(tmpFile.Name())
contents, err := getComposeFile()
if err != nil {
return fmt.Errorf("Unable to read compose template: %s", err)
}
tmpl, err := template.New("compose.yml").Parse(string(contents))
if err != nil {
return fmt.Errorf("Unable to parse compose template: %s", err)
}
dokkuLibRoot := os.Getenv("DOKKU_LIB_HOST_ROOT")
if dokkuLibRoot == "" {
dokkuLibRoot = os.Getenv("DOKKU_LIB_ROOT")
}
dokkuLogsDir := os.Getenv("DOKKU_LOGS_HOST_DIR")
if dokkuLogsDir == "" {
dokkuLogsDir = os.Getenv("DOKKU_LOGS_DIR")
}
data := vectorTemplateData{
DokkuLibRoot: dokkuLibRoot,
DokkuLogsDir: dokkuLogsDir,
VectorImage: vectorImage,
}
if err := tmpl.Execute(tmpFile, data); err != nil {
return fmt.Errorf("Unable to execute compose template: %s", err)
}
return common.ComposeUp(common.ComposeUpInput{
ProjectName: "vector",
ComposeFile: tmpFile.Name(),
})
}
func getComputedVectorImage() string {
return common.PropertyGetDefault("logs", "--global", "vector-image", getDefaultVectorImage())
}
// getDefaultVectorImage returns the default image used for the vector container
func getDefaultVectorImage() string {
contents := strings.TrimSpace(VectorDockerfile)
parts := strings.SplitN(contents, " ", 2)
return parts[1]
}
func stopVectorContainer() error {
if !common.IsComposeInstalled() {
return errors.New("Required docker compose plugin is not installed")
}
if common.ContainerExists(vectorOldContainerName) {
common.ContainerRemove(vectorOldContainerName)
}
tmpFile, err := os.CreateTemp(os.TempDir(), "vector-compose-*.yml")
if err != nil {
return fmt.Errorf("Unable to create temporary file: %s", err)
}
defer os.Remove(tmpFile.Name())
contents, err := getComposeFile()
if err != nil {
return fmt.Errorf("Unable to read compose template: %s", err)
}
tmpl, err := template.New("compose.yml").Parse(string(contents))
if err != nil {
return fmt.Errorf("Unable to parse compose template: %s", err)
}
dokkuLibRoot := os.Getenv("DOKKU_LIB_HOST_ROOT")
if dokkuLibRoot == "" {
dokkuLibRoot = os.Getenv("DOKKU_LIB_ROOT")
}
dokkuLogsDir := os.Getenv("DOKKU_LOGS_HOST_DIR")
if dokkuLogsDir == "" {
dokkuLogsDir = os.Getenv("DOKKU_LOGS_DIR")
}
data := vectorTemplateData{
DokkuLibRoot: dokkuLibRoot,
DokkuLogsDir: dokkuLogsDir,
VectorImage: getComputedVectorImage(),
}
if err := tmpl.Execute(tmpFile, data); err != nil {
return fmt.Errorf("Unable to execute compose template: %s", err)
}
return common.ComposeDown(common.ComposeDownInput{
ProjectName: "vector",
ComposeFile: tmpFile.Name(),
})
}
func writeVectorConfig() error {
apps, _ := common.UnfilteredDokkuApps()
data := vectorConfig{
Sources: map[string]vectorSource{},
Sinks: map[string]VectorSink{},
}
for _, appName := range apps {
value := common.PropertyGet("logs", appName, "vector-sink")
if value == "" {
continue
}
inflectedAppName := strings.ReplaceAll(appName, ".", "-")
sink, err := SinkValueToConfig(inflectedAppName, value)
if err != nil {
return err
}
data.Sources[fmt.Sprintf("docker-source:%s", inflectedAppName)] = vectorSource{
Type: "docker_logs",
IncludeLabels: []string{fmt.Sprintf("%s=%s", reportComputedAppLabelAlias(appName), appName)},
}
data.Sinks[fmt.Sprintf("docker-sink:%s", inflectedAppName)] = sink
}
value := common.PropertyGet("logs", "--global", "vector-sink")
if value != "" {
sink, err := SinkValueToConfig("--global", value)
if err != nil {
return err
}
data.Sources["docker-global-source"] = vectorSource{
Type: "docker_logs",
IncludeLabels: []string{reportGlobalAppLabelAlias("global")},
}
data.Sinks["docker-global-sink"] = sink
}
if len(data.Sources) == 0 {
// pull from no containers
data.Sources["docker-null-source"] = vectorSource{
Type: "docker_logs",
IncludeLabels: []string{"com.dokku.vector-null"},
}
}
if len(data.Sinks) == 0 {
// write logs to a blackhole
sink, err := SinkValueToConfig("--null", VectorDefaultSink)
if err != nil {
return err
}
data.Sinks["docker-null-sink"] = sink
}
b, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)
b = bytes.Replace(b, []byte("\\u002B"), []byte("+"), -1)
vectorConfig := filepath.Join(common.GetDataDirectory("logs"), "vector.json")
if err := common.WriteBytesToFile(common.WriteBytesToFileInput{
Bytes: b,
Filename: vectorConfig,
Mode: os.FileMode(0600),
}); err != nil {
return err
}
return nil
}