Files
dokku/plugins/logs/functions.go
Jose Diaz-Gonzalez 268c318bc5 fix: ensure we do not filter apps for certain commands and triggers
Some things must pull *all* apps vs filtered apps (mostly things that write out config files or install triggers). Using an unflitered list is best.

Also, we default to filtering, which should be what most usage needs.
2022-05-15 15:47:13 -04:00

232 lines
5.2 KiB
Go

package logs
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/url"
"path/filepath"
"strings"
"time"
"github.com/dokku/dokku/plugins/common"
"github.com/joncalhoun/qson"
)
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 vectorSink map[string]interface{}
const vectorContainerName = "vector"
func killVectorContainer() error {
if !common.ContainerExists(vectorContainerName) {
return nil
}
if err := stopVectorContainer(); err != nil {
return err
}
time.Sleep(10 * time.Second)
if err := removeVectorContainer(); err != nil {
return err
}
return nil
}
func removeVectorContainer() error {
if !common.ContainerExists(vectorContainerName) {
return nil
}
cmd := common.NewShellCmd(strings.Join([]string{
common.DockerBin(), "container", "rm", "-f", vectorContainerName}, " "))
return common.SuppressOutput(func() error {
if cmd.Execute() {
return nil
}
if common.ContainerExists(vectorContainerName) {
return errors.New("Unable to remove vector container")
}
return nil
})
}
func startVectorContainer(vectorImage string) error {
cmd := common.NewShellCmd(strings.Join([]string{
common.DockerBin(),
"container",
"run", "--detach", "--name", vectorContainerName, common.MustGetEnv("DOKKU_GLOBAL_RUN_ARGS"),
"--restart", "unless-stopped",
"--volume", "/var/lib/dokku/data/logs/vector.json:/etc/vector/vector.json",
"--volume", "/var/run/docker.sock:/var/run/docker.sock",
"--volume", common.MustGetEnv("DOKKU_LOGS_HOST_DIR") + ":/var/logs/dokku/apps",
"--volume", common.MustGetEnv("DOKKU_LOGS_HOST_DIR") + "/apps:/var/log/dokku/apps",
vectorImage,
"--config", "/etc/vector/vector.json", "--watch-config", "1"}, " "))
cmd.ShowOutput = false
if !cmd.Execute() {
return errors.New("Unable to start vector container")
}
return nil
}
func stopVectorContainer() error {
if !common.ContainerExists(vectorContainerName) {
return nil
}
if !common.ContainerIsRunning(vectorContainerName) {
return nil
}
cmd := common.NewShellCmd(strings.Join([]string{
common.DockerBin(), "container", "stop", vectorContainerName}, " "))
return common.SuppressOutput(func() error {
if cmd.Execute() {
return nil
}
if common.ContainerIsRunning(vectorContainerName) {
return errors.New("Unable to stop vector container")
}
return nil
})
}
func sinkValueToConfig(appName string, sinkValue string) (vectorSink, error) {
var data vectorSink
if strings.Contains(sinkValue, "://") {
parts := strings.SplitN(sinkValue, "://", 2)
parts[0] = strings.ReplaceAll(parts[0], "_", "-")
sinkValue = strings.Join(parts, "://")
}
u, err := url.Parse(sinkValue)
if err != nil {
return data, err
}
if u.Query().Get("sinks") != "" {
return data, errors.New("Invalid option sinks")
}
u.Scheme = strings.ReplaceAll(u.Scheme, "-", "_")
query := u.RawQuery
if strings.HasPrefix(query, "&") {
query = strings.TrimPrefix(query, "&")
}
b, err := qson.ToJSON(query)
if err != nil {
return data, err
}
if err := json.Unmarshal(b, &data); err != nil {
return data, err
}
data["type"] = u.Scheme
data["inputs"] = []string{"docker-source:" + appName}
if appName == "--global" {
data["inputs"] = []string{"docker-global-source"}
}
if appName == "--null" {
data["inputs"] = []string{"docker-null-source"}
}
return data, nil
}
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
}
appName = strings.ReplaceAll(appName, ".", "-")
sink, err := sinkValueToConfig(appName, value)
if err != nil {
return err
}
data.Sources[fmt.Sprintf("docker-source:%s", appName)] = vectorSource{
Type: "docker_logs",
IncludeLabels: []string{fmt.Sprintf("com.dokku.app-name=%s", appName)},
}
data.Sinks[fmt.Sprintf("docker-sink:%s", appName)] = 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{"com.dokku.app-name"},
}
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)
vectorConfig := filepath.Join(common.MustGetEnv("DOKKU_LIB_ROOT"), "data", "logs", "vector.json")
if err := common.WriteSliceToFile(vectorConfig, []string{string(b)}); err != nil {
return err
}
return nil
}