github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/cmd/docker-driver/config.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/url"
     8  	"os"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/docker/docker/daemon/logger"
    14  	"github.com/docker/docker/daemon/logger/templates"
    15  	"github.com/grafana/dskit/backoff"
    16  	"github.com/grafana/dskit/flagext"
    17  	"github.com/pkg/errors"
    18  	"github.com/prometheus/common/model"
    19  	"github.com/prometheus/prometheus/model/labels"
    20  	"github.com/prometheus/prometheus/model/relabel"
    21  	"gopkg.in/yaml.v2"
    22  
    23  	"github.com/grafana/loki/clients/pkg/logentry/stages"
    24  	"github.com/grafana/loki/clients/pkg/promtail/client"
    25  	"github.com/grafana/loki/clients/pkg/promtail/targets/file"
    26  
    27  	"github.com/grafana/loki/pkg/util"
    28  )
    29  
    30  const (
    31  	driverName = "loki"
    32  
    33  	cfgExternalLabelsKey     = "loki-external-labels"
    34  	cfgURLKey                = "loki-url"
    35  	cfgTLSCAFileKey          = "loki-tls-ca-file"
    36  	cfgTLSCertFileKey        = "loki-tls-cert-file"
    37  	cfgTLSKeyFileKey         = "loki-tls-key-file"
    38  	cfgTLSServerNameKey      = "loki-tls-server-name"
    39  	cfgTLSInsecure           = "loki-tls-insecure-skip-verify"
    40  	cfgProxyURLKey           = "loki-proxy-url"
    41  	cfgTimeoutKey            = "loki-timeout"
    42  	cfgBatchWaitKey          = "loki-batch-wait"
    43  	cfgBatchSizeKey          = "loki-batch-size"
    44  	cfgMinBackoffKey         = "loki-min-backoff"
    45  	cfgMaxBackoffKey         = "loki-max-backoff"
    46  	cfgMaxRetriesKey         = "loki-retries"
    47  	cfgPipelineStagesFileKey = "loki-pipeline-stage-file"
    48  	cfgPipelineStagesKey     = "loki-pipeline-stages"
    49  	cfgTenantIDKey           = "loki-tenant-id"
    50  	cfgNofile                = "no-file"
    51  	cfgKeepFile              = "keep-file"
    52  	cfgRelabelKey            = "loki-relabel-config"
    53  
    54  	swarmServiceLabelKey = "com.docker.swarm.service.name"
    55  	swarmStackLabelKey   = "com.docker.stack.namespace"
    56  
    57  	swarmServiceLabelName = "swarm_service"
    58  	swarmStackLabelName   = "swarm_stack"
    59  
    60  	composeServiceLabelKey = "com.docker.compose.service"
    61  	composeProjectLabelKey = "com.docker.compose.project"
    62  
    63  	composeServiceLabelName = "compose_service"
    64  	composeProjectLabelName = "compose_project"
    65  
    66  	defaultExternalLabels = "container_name={{.Name}}"
    67  	defaultHostLabelName  = model.LabelName("host")
    68  )
    69  
    70  var (
    71  	defaultClientConfig = client.Config{
    72  		BatchWait: client.BatchWait,
    73  		BatchSize: client.BatchSize,
    74  		BackoffConfig: backoff.Config{
    75  			MinBackoff: client.MinBackoff,
    76  			MaxBackoff: client.MaxBackoff,
    77  			MaxRetries: client.MaxRetries,
    78  		},
    79  		Timeout: client.Timeout,
    80  	}
    81  )
    82  
    83  type config struct {
    84  	labels       model.LabelSet
    85  	clientConfig client.Config
    86  	pipeline     PipelineConfig
    87  }
    88  
    89  type PipelineConfig struct {
    90  	PipelineStages stages.PipelineStages `yaml:"pipeline_stages,omitempty"`
    91  }
    92  
    93  func validateDriverOpt(loggerInfo logger.Info) error {
    94  	config := loggerInfo.Config
    95  
    96  	for opt := range config {
    97  		switch opt {
    98  		case cfgURLKey:
    99  		case cfgExternalLabelsKey:
   100  		case cfgTLSCAFileKey:
   101  		case cfgTLSCertFileKey:
   102  		case cfgTLSKeyFileKey:
   103  		case cfgTLSServerNameKey:
   104  		case cfgTLSInsecure:
   105  		case cfgTimeoutKey:
   106  		case cfgProxyURLKey:
   107  		case cfgBatchWaitKey:
   108  		case cfgBatchSizeKey:
   109  		case cfgMinBackoffKey:
   110  		case cfgMaxBackoffKey:
   111  		case cfgMaxRetriesKey:
   112  		case cfgPipelineStagesKey:
   113  		case cfgPipelineStagesFileKey:
   114  		case cfgTenantIDKey:
   115  		case cfgRelabelKey:
   116  		case cfgNofile:
   117  		case cfgKeepFile:
   118  		case "labels":
   119  		case "env":
   120  		case "env-regex":
   121  		case "max-size":
   122  		case "max-file":
   123  		case "mode":
   124  		case "max-buffer-size":
   125  		default:
   126  			return fmt.Errorf("%s: wrong log-opt: '%s' - %s", driverName, opt, loggerInfo.ContainerID)
   127  		}
   128  	}
   129  	_, ok := config[cfgURLKey]
   130  	if !ok {
   131  		return fmt.Errorf("%s: %s is required in the config", driverName, cfgURLKey)
   132  	}
   133  
   134  	return nil
   135  }
   136  
   137  func parseConfig(logCtx logger.Info) (*config, error) {
   138  	if err := validateDriverOpt(logCtx); err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	clientConfig := defaultClientConfig
   143  	labels := model.LabelSet{}
   144  
   145  	// parse URL
   146  	rawURL, ok := logCtx.Config[cfgURLKey]
   147  	if !ok {
   148  		return nil, fmt.Errorf("%s: option %s is required", driverName, cfgURLKey)
   149  	}
   150  	url, err := url.Parse(rawURL)
   151  	if err != nil {
   152  		return nil, fmt.Errorf("%s: option %s is invalid %s", driverName, cfgURLKey, err)
   153  	}
   154  	clientConfig.URL = flagext.URLValue{URL: url}
   155  
   156  	// parse timeout
   157  	if err := parseDuration(cfgTimeoutKey, logCtx, func(d time.Duration) { clientConfig.Timeout = d }); err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	// parse batch wait and batch size
   162  	if err := parseDuration(cfgBatchWaitKey, logCtx, func(d time.Duration) { clientConfig.BatchWait = d }); err != nil {
   163  		return nil, err
   164  	}
   165  	if err := parseInt(cfgBatchSizeKey, logCtx, func(i int) { clientConfig.BatchSize = i }); err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	// parse backoff
   170  	if err := parseDuration(cfgMinBackoffKey, logCtx, func(d time.Duration) { clientConfig.BackoffConfig.MinBackoff = d }); err != nil {
   171  		return nil, err
   172  	}
   173  	if err := parseDuration(cfgMaxBackoffKey, logCtx, func(d time.Duration) { clientConfig.BackoffConfig.MaxBackoff = d }); err != nil {
   174  		return nil, err
   175  	}
   176  	if err := parseInt(cfgMaxRetriesKey, logCtx, func(i int) { clientConfig.BackoffConfig.MaxRetries = i }); err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	// parse http & tls config
   181  	if tlsCAFile, ok := logCtx.Config[cfgTLSCAFileKey]; ok {
   182  		clientConfig.Client.TLSConfig.CAFile = tlsCAFile
   183  	}
   184  	if tlsCertFile, ok := logCtx.Config[cfgTLSCertFileKey]; ok {
   185  		clientConfig.Client.TLSConfig.CertFile = tlsCertFile
   186  	}
   187  	if tlsKeyFile, ok := logCtx.Config[cfgTLSKeyFileKey]; ok {
   188  		clientConfig.Client.TLSConfig.KeyFile = tlsKeyFile
   189  	}
   190  	if tlsServerName, ok := logCtx.Config[cfgTLSServerNameKey]; ok {
   191  		clientConfig.Client.TLSConfig.ServerName = tlsServerName
   192  	}
   193  	if tlsInsecureSkipRaw, ok := logCtx.Config[cfgTLSInsecure]; ok {
   194  		tlsInsecureSkip, err := strconv.ParseBool(tlsInsecureSkipRaw)
   195  		if err != nil {
   196  			return nil, fmt.Errorf("%s: invalid external labels: %s", driverName, tlsInsecureSkipRaw)
   197  		}
   198  		clientConfig.Client.TLSConfig.InsecureSkipVerify = tlsInsecureSkip
   199  	}
   200  	if tlsProxyURL, ok := logCtx.Config[cfgProxyURLKey]; ok {
   201  		proxyURL, err := url.Parse(tlsProxyURL)
   202  		if err != nil {
   203  			return nil, fmt.Errorf("%s: option %s is invalid %s", driverName, cfgProxyURLKey, err)
   204  		}
   205  		clientConfig.Client.ProxyURL.URL = proxyURL
   206  	}
   207  
   208  	// parse tenant id
   209  	tenantID, ok := logCtx.Config[cfgTenantIDKey]
   210  	if ok && tenantID != "" {
   211  		clientConfig.TenantID = tenantID
   212  	}
   213  
   214  	// parse external labels
   215  	extlbs, ok := logCtx.Config[cfgExternalLabelsKey]
   216  	if !ok {
   217  		extlbs = defaultExternalLabels
   218  	}
   219  	lvs := strings.Split(extlbs, ",")
   220  	for _, lv := range lvs {
   221  		lvparts := strings.Split(lv, "=")
   222  		if len(lvparts) != 2 {
   223  			return nil, fmt.Errorf("%s: invalid external labels: %s", driverName, extlbs)
   224  		}
   225  		labelName := model.LabelName(lvparts[0])
   226  		if !labelName.IsValid() {
   227  			return nil, fmt.Errorf("%s: invalid external label name: %s", driverName, labelName)
   228  		}
   229  
   230  		// expand the value using docker template {{.Name}}.{{.ImageName}}
   231  		value, err := expandLabelValue(logCtx, lvparts[1])
   232  		if err != nil {
   233  			return nil, fmt.Errorf("%s: could not expand label value: %s err : %s", driverName, lvparts[1], err)
   234  		}
   235  		labelValue := model.LabelValue(value)
   236  		if !labelValue.IsValid() {
   237  			return nil, fmt.Errorf("%s: invalid external label value: %s", driverName, value)
   238  		}
   239  		labels[labelName] = labelValue
   240  	}
   241  
   242  	// other labels coming from docker labels or env selected by user labels, labels-regex, env, env-regex config.
   243  	attrs, err := logCtx.ExtraAttributes(func(label string) string {
   244  		return strings.ReplaceAll(strings.ReplaceAll(label, "-", "_"), ".", "_")
   245  	})
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  
   250  	// parse docker swarms labels and adds them automatically to attrs
   251  	swarmService := logCtx.ContainerLabels[swarmServiceLabelKey]
   252  	if swarmService != "" {
   253  		attrs[swarmServiceLabelName] = swarmService
   254  	}
   255  	swarmStack := logCtx.ContainerLabels[swarmStackLabelKey]
   256  	if swarmStack != "" {
   257  		attrs[swarmStackLabelName] = swarmStack
   258  	}
   259  
   260  	// parse docker compose labels and adds them automatically to attrs
   261  	composeService := logCtx.ContainerLabels[composeServiceLabelKey]
   262  	if composeService != "" {
   263  		attrs[composeServiceLabelName] = composeService
   264  	}
   265  	composeProject := logCtx.ContainerLabels[composeProjectLabelKey]
   266  	if composeProject != "" {
   267  		attrs[composeProjectLabelName] = composeProject
   268  	}
   269  
   270  	for key, value := range attrs {
   271  		labelName := model.LabelName(key)
   272  		if !labelName.IsValid() {
   273  			return nil, fmt.Errorf("%s: invalid label name from attribute: %s", driverName, key)
   274  		}
   275  		labelValue := model.LabelValue(value)
   276  		if !labelValue.IsValid() {
   277  			return nil, fmt.Errorf("%s: invalid label value from attribute: %s", driverName, value)
   278  		}
   279  		labels[labelName] = labelValue
   280  	}
   281  
   282  	// adds host label and filename
   283  	host, err := os.Hostname()
   284  	if err == nil {
   285  		labels[defaultHostLabelName] = model.LabelValue(host)
   286  	}
   287  	labels[file.FilenameLabel] = model.LabelValue(logCtx.LogPath)
   288  
   289  	// Process relabel configs.
   290  	if relabelString, ok := logCtx.Config[cfgRelabelKey]; ok && relabelString != "" {
   291  		relabeled, err := relabelConfig(relabelString, labels)
   292  		if err != nil {
   293  			return nil, fmt.Errorf("error applying relabel config: %s err: %s", relabelString, err)
   294  		}
   295  		labels = relabeled
   296  	}
   297  
   298  	// parse pipeline stages
   299  	pipeline, err := parsePipeline(logCtx)
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  	return &config{
   304  		labels:       labels,
   305  		clientConfig: clientConfig,
   306  		pipeline:     pipeline,
   307  	}, nil
   308  }
   309  
   310  func parsePipeline(logCtx logger.Info) (PipelineConfig, error) {
   311  	var pipeline PipelineConfig
   312  	pipelineFile, okFile := logCtx.Config[cfgPipelineStagesFileKey]
   313  	pipelineString, okString := logCtx.Config[cfgPipelineStagesKey]
   314  	if okFile && okString {
   315  		return pipeline, fmt.Errorf("only one of %s or %s can be configured", cfgPipelineStagesFileKey, cfgPipelineStagesFileKey)
   316  	}
   317  	if okFile {
   318  		if err := loadConfig(pipelineFile, &pipeline); err != nil {
   319  			return pipeline, fmt.Errorf("error loading config file %s: %s", pipelineFile, err)
   320  		}
   321  	}
   322  	if okString {
   323  		if err := yaml.UnmarshalStrict([]byte(pipelineString), &pipeline.PipelineStages); err != nil {
   324  			return pipeline, err
   325  		}
   326  	}
   327  	return pipeline, nil
   328  }
   329  
   330  func expandLabelValue(info logger.Info, defaultTemplate string) (string, error) {
   331  	tmpl, err := templates.NewParse("label_value", defaultTemplate)
   332  	if err != nil {
   333  		return "", err
   334  	}
   335  	buf := new(bytes.Buffer)
   336  	if err := tmpl.Execute(buf, &info); err != nil {
   337  		return "", err
   338  	}
   339  
   340  	return buf.String(), nil
   341  }
   342  
   343  func parseDuration(key string, logCtx logger.Info, set func(d time.Duration)) error {
   344  	if raw, ok := logCtx.Config[key]; ok {
   345  		val, err := time.ParseDuration(raw)
   346  		if err != nil {
   347  			return fmt.Errorf("%s: invalid option %s format: %s", driverName, key, raw)
   348  		}
   349  		set(val)
   350  	}
   351  	return nil
   352  }
   353  
   354  func parseInt(key string, logCtx logger.Info, set func(i int)) error {
   355  	if raw, ok := logCtx.Config[key]; ok {
   356  		val, err := strconv.Atoi(raw)
   357  		if err != nil {
   358  			return fmt.Errorf("%s: invalid option %s format: %s", driverName, key, raw)
   359  		}
   360  		set(val)
   361  	}
   362  	return nil
   363  }
   364  
   365  func relabelConfig(config string, lbs model.LabelSet) (model.LabelSet, error) {
   366  	relabelConfig := make([]*relabel.Config, 0)
   367  	if err := yaml.UnmarshalStrict([]byte(config), &relabelConfig); err != nil {
   368  		return nil, err
   369  	}
   370  	relabed := relabel.Process(labels.FromMap(util.ModelLabelSetToMap(lbs)), relabelConfig...)
   371  	return model.LabelSet(util.LabelsToMetric(relabed)), nil
   372  }
   373  
   374  func parseBoolean(key string, logCtx logger.Info, defaultValue bool) (bool, error) {
   375  	value, ok := logCtx.Config[key]
   376  	if !ok || value == "" {
   377  		return defaultValue, nil
   378  	}
   379  	b, err := strconv.ParseBool(value)
   380  	if err != nil {
   381  		return false, err
   382  	}
   383  	return b, nil
   384  }
   385  
   386  // loadConfig read YAML-formatted config from filename into cfg.
   387  func loadConfig(filename string, cfg interface{}) error {
   388  	buf, err := ioutil.ReadFile(filename)
   389  	if err != nil {
   390  		return errors.Wrap(err, "Error reading config file")
   391  	}
   392  
   393  	return yaml.UnmarshalStrict(buf, cfg)
   394  }