2021-01-04 00:30:22 -05:00
|
|
|
package logs
|
|
|
|
|
|
|
|
|
|
import (
|
2021-09-10 23:04:41 -04:00
|
|
|
"bytes"
|
2021-01-04 00:30:22 -05:00
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
2024-01-26 06:45:24 -05:00
|
|
|
"html/template"
|
2024-01-18 19:11:53 -05:00
|
|
|
"os"
|
2021-01-04 00:30:22 -05:00
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/dokku/dokku/plugins/common"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type vectorConfig struct {
|
|
|
|
|
Sources map[string]vectorSource `json:"sources"`
|
2025-04-14 04:35:21 -04:00
|
|
|
Sinks map[string]VectorSink `json:"sinks"`
|
2021-01-04 00:30:22 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type vectorSource struct {
|
|
|
|
|
Type string `json:"type"`
|
|
|
|
|
IncludeLabels []string `json:"include_labels,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
type vectorTemplateData struct {
|
|
|
|
|
DokkuLibRoot string
|
|
|
|
|
DokkuLogsDir string
|
|
|
|
|
VectorImage string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const vectorContainerName = "vector-vector-1"
|
|
|
|
|
const vectorOldContainerName = "vector"
|
2021-01-04 00:30:22 -05:00
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
func getComposeFile() ([]byte, error) {
|
|
|
|
|
result, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
|
2024-02-12 20:28:31 -05:00
|
|
|
Trigger: "vector-template-source",
|
2024-01-26 06:45:24 -05:00
|
|
|
})
|
|
|
|
|
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)
|
|
|
|
|
}
|
2021-01-04 00:30:22 -05:00
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
return contents, nil
|
2021-01-04 00:30:22 -05:00
|
|
|
}
|
|
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
contents, err := templates.ReadFile("templates/compose.yml.tmpl")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return []byte{}, fmt.Errorf("Unable to read compose template: %s", err)
|
2021-01-04 00:30:22 -05:00
|
|
|
}
|
|
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
return contents, nil
|
2021-01-04 00:30:22 -05:00
|
|
|
}
|
|
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
func startVectorContainer(vectorImage string) error {
|
|
|
|
|
if !common.IsComposeInstalled() {
|
|
|
|
|
return errors.New("Required docker compose plugin is not installed")
|
2021-01-04 00:30:22 -05:00
|
|
|
}
|
|
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
if common.ContainerExists(vectorOldContainerName) {
|
|
|
|
|
return errors.New("Vector container %s already exists in old format, run 'dokku logs:vector-stop' once to remove it")
|
|
|
|
|
}
|
2021-01-04 00:30:22 -05:00
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
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())
|
2021-01-04 00:30:22 -05:00
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
contents, err := getComposeFile()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Unable to read compose template: %s", err)
|
|
|
|
|
}
|
2021-01-04 00:30:22 -05:00
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
tmpl, err := template.New("compose.yml").Parse(string(contents))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Unable to parse compose template: %s", err)
|
|
|
|
|
}
|
2021-01-04 00:30:22 -05:00
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-24 23:31:34 -04:00
|
|
|
return common.ComposeUp(common.ComposeUpInput{
|
|
|
|
|
ProjectName: "vector",
|
|
|
|
|
ComposeFile: tmpFile.Name(),
|
2024-01-26 06:45:24 -05:00
|
|
|
})
|
2021-01-04 00:30:22 -05:00
|
|
|
}
|
|
|
|
|
|
2024-02-25 00:54:11 -05:00
|
|
|
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]
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-04 00:30:22 -05:00
|
|
|
func stopVectorContainer() error {
|
2024-01-26 06:45:24 -05:00
|
|
|
if !common.IsComposeInstalled() {
|
|
|
|
|
return errors.New("Required docker compose plugin is not installed")
|
2021-01-04 00:30:22 -05:00
|
|
|
}
|
|
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
if common.ContainerExists(vectorOldContainerName) {
|
|
|
|
|
common.ContainerRemove(vectorOldContainerName)
|
2021-01-04 00:30:22 -05:00
|
|
|
}
|
|
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
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())
|
2021-01-04 00:30:22 -05:00
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
contents, err := getComposeFile()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Unable to read compose template: %s", err)
|
|
|
|
|
}
|
2021-01-04 00:30:22 -05:00
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
tmpl, err := template.New("compose.yml").Parse(string(contents))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Unable to parse compose template: %s", err)
|
|
|
|
|
}
|
2021-01-04 00:30:22 -05:00
|
|
|
|
2024-01-26 06:45:24 -05:00
|
|
|
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,
|
2024-02-25 00:54:11 -05:00
|
|
|
VectorImage: getComputedVectorImage(),
|
2024-01-26 06:45:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := tmpl.Execute(tmpFile, data); err != nil {
|
|
|
|
|
return fmt.Errorf("Unable to execute compose template: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-24 23:31:34 -04:00
|
|
|
return common.ComposeDown(common.ComposeDownInput{
|
|
|
|
|
ProjectName: "vector",
|
|
|
|
|
ComposeFile: tmpFile.Name(),
|
2021-01-04 00:30:22 -05:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func writeVectorConfig() error {
|
2022-05-15 15:47:13 -04:00
|
|
|
apps, _ := common.UnfilteredDokkuApps()
|
2021-01-04 00:30:22 -05:00
|
|
|
data := vectorConfig{
|
|
|
|
|
Sources: map[string]vectorSource{},
|
2025-04-14 04:35:21 -04:00
|
|
|
Sinks: map[string]VectorSink{},
|
2021-01-04 00:30:22 -05:00
|
|
|
}
|
|
|
|
|
for _, appName := range apps {
|
|
|
|
|
value := common.PropertyGet("logs", appName, "vector-sink")
|
|
|
|
|
if value == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-25 01:06:28 -04:00
|
|
|
inflectedAppName := strings.ReplaceAll(appName, ".", "-")
|
2025-04-14 04:35:21 -04:00
|
|
|
sink, err := SinkValueToConfig(inflectedAppName, value)
|
2021-01-04 00:30:22 -05:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-25 01:06:28 -04:00
|
|
|
data.Sources[fmt.Sprintf("docker-source:%s", inflectedAppName)] = vectorSource{
|
2021-01-04 00:30:22 -05:00
|
|
|
Type: "docker_logs",
|
2025-01-03 23:37:49 -05:00
|
|
|
IncludeLabels: []string{fmt.Sprintf("%s=%s", reportComputedAppLabelAlias(appName), appName)},
|
2021-01-04 00:30:22 -05:00
|
|
|
}
|
|
|
|
|
|
2022-06-25 01:06:28 -04:00
|
|
|
data.Sinks[fmt.Sprintf("docker-sink:%s", inflectedAppName)] = sink
|
2021-01-04 00:30:22 -05:00
|
|
|
}
|
|
|
|
|
|
2021-01-04 01:11:49 -05:00
|
|
|
value := common.PropertyGet("logs", "--global", "vector-sink")
|
|
|
|
|
if value != "" {
|
2025-04-14 04:35:21 -04:00
|
|
|
sink, err := SinkValueToConfig("--global", value)
|
2021-01-04 01:11:49 -05:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data.Sources["docker-global-source"] = vectorSource{
|
|
|
|
|
Type: "docker_logs",
|
2025-01-03 23:37:49 -05:00
|
|
|
IncludeLabels: []string{reportGlobalAppLabelAlias("global")},
|
2021-01-04 01:11:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data.Sinks["docker-global-sink"] = sink
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-04 01:12:25 -05:00
|
|
|
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
|
2025-04-14 04:35:21 -04:00
|
|
|
sink, err := SinkValueToConfig("--null", VectorDefaultSink)
|
2021-01-04 01:12:25 -05:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data.Sinks["docker-null-sink"] = sink
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-04 00:30:22 -05:00
|
|
|
b, err := json.MarshalIndent(data, "", " ")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-10 23:04:41 -04:00
|
|
|
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)
|
2022-12-04 15:40:46 -05:00
|
|
|
b = bytes.Replace(b, []byte("\\u002B"), []byte("+"), -1)
|
2021-09-10 23:04:41 -04:00
|
|
|
|
2023-01-08 23:24:15 -05:00
|
|
|
vectorConfig := filepath.Join(common.GetDataDirectory("logs"), "vector.json")
|
2024-02-06 01:39:44 -05:00
|
|
|
if err := common.WriteBytesToFile(common.WriteBytesToFileInput{
|
|
|
|
|
Bytes: b,
|
2024-01-18 19:11:53 -05:00
|
|
|
Filename: vectorConfig,
|
|
|
|
|
Mode: os.FileMode(0600),
|
|
|
|
|
}); err != nil {
|
2021-01-04 00:30:22 -05:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-04 21:48:28 -05:00
|
|
|
return nil
|
2021-01-04 00:30:22 -05:00
|
|
|
}
|