github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/configs/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"encoding/json"
     7  	"errors"
     8  	"flag"
     9  	"fmt"
    10  	"net/http"
    11  	"net/url"
    12  	"time"
    13  
    14  	"github.com/go-kit/log/level"
    15  	dstls "github.com/grafana/dskit/crypto/tls"
    16  	"github.com/grafana/dskit/flagext"
    17  	"github.com/prometheus/client_golang/prometheus"
    18  	"github.com/prometheus/client_golang/prometheus/promauto"
    19  	"github.com/prometheus/common/version"
    20  	"github.com/weaveworks/common/instrument"
    21  
    22  	"github.com/grafana/loki/pkg/configs/userconfig"
    23  	util_log "github.com/grafana/loki/pkg/util/log"
    24  )
    25  
    26  var (
    27  	errBadURL = errors.New("configs_api_url is not set or valid")
    28  )
    29  
    30  // Config says where we can find the ruler userconfig.
    31  type Config struct {
    32  	ConfigsAPIURL flagext.URLValue   `yaml:"configs_api_url"`
    33  	ClientTimeout time.Duration      `yaml:"client_timeout"` // HTTP timeout duration for requests made to the Weave Cloud configs service.
    34  	TLS           dstls.ClientConfig `yaml:",inline"`
    35  }
    36  
    37  // RegisterFlagsWithPrefix adds the flags required to config this to the given FlagSet
    38  func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
    39  	f.Var(&cfg.ConfigsAPIURL, prefix+"configs.url", "URL of configs API server.")
    40  	f.DurationVar(&cfg.ClientTimeout, prefix+"configs.client-timeout", 5*time.Second, "Timeout for requests to Weave Cloud configs service.")
    41  	cfg.TLS.RegisterFlagsWithPrefix(prefix+"configs", f)
    42  }
    43  
    44  var configsRequestDuration = instrument.NewHistogramCollector(promauto.NewHistogramVec(prometheus.HistogramOpts{
    45  	Namespace: "cortex",
    46  	Name:      "configs_request_duration_seconds",
    47  	Help:      "Time spent requesting userconfig.",
    48  	Buckets:   prometheus.DefBuckets,
    49  }, []string{"operation", "status_code"}))
    50  
    51  // Client is what the ruler and altermanger needs from a config store to process rules.
    52  type Client interface {
    53  	// GetRules returns all Cortex configurations from a configs API server
    54  	// that have been updated after the given userconfig.ID was last updated.
    55  	GetRules(ctx context.Context, since userconfig.ID) (map[string]userconfig.VersionedRulesConfig, error)
    56  
    57  	// GetAlerts fetches all the alerts that have changes since since.
    58  	GetAlerts(ctx context.Context, since userconfig.ID) (*ConfigsResponse, error)
    59  }
    60  
    61  // New creates a new ConfigClient.
    62  func New(cfg Config) (*ConfigDBClient, error) {
    63  
    64  	if cfg.ConfigsAPIURL.URL == nil {
    65  		return nil, errBadURL
    66  	}
    67  
    68  	client := &ConfigDBClient{
    69  		URL:     cfg.ConfigsAPIURL.URL,
    70  		Timeout: cfg.ClientTimeout,
    71  	}
    72  
    73  	tlsConfig, err := cfg.TLS.GetTLSConfig()
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	if tlsConfig != nil {
    79  		client.TLSConfig = tlsConfig
    80  	}
    81  	return client, nil
    82  }
    83  
    84  // ConfigDBClient allows retrieving recording and alerting rules from the configs server.
    85  type ConfigDBClient struct {
    86  	URL       *url.URL
    87  	Timeout   time.Duration
    88  	TLSConfig *tls.Config
    89  }
    90  
    91  // GetRules implements Client
    92  func (c ConfigDBClient) GetRules(ctx context.Context, since userconfig.ID) (map[string]userconfig.VersionedRulesConfig, error) {
    93  	suffix := ""
    94  	if since != 0 {
    95  		suffix = fmt.Sprintf("?since=%d", since)
    96  	}
    97  	endpoint := fmt.Sprintf("%s/private/api/prom/configs/rules%s", c.URL.String(), suffix)
    98  	var response *ConfigsResponse
    99  	err := instrument.CollectedRequest(ctx, "GetRules", configsRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
   100  		var err error
   101  		response, err = doRequest(endpoint, c.Timeout, c.TLSConfig, since)
   102  		return err
   103  	})
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	configs := map[string]userconfig.VersionedRulesConfig{}
   108  	for id, view := range response.Configs {
   109  		cfg := view.GetVersionedRulesConfig()
   110  		if cfg != nil {
   111  			configs[id] = *cfg
   112  		}
   113  	}
   114  	return configs, nil
   115  }
   116  
   117  // GetAlerts implements Client.
   118  func (c ConfigDBClient) GetAlerts(ctx context.Context, since userconfig.ID) (*ConfigsResponse, error) {
   119  	suffix := ""
   120  	if since != 0 {
   121  		suffix = fmt.Sprintf("?since=%d", since)
   122  	}
   123  	endpoint := fmt.Sprintf("%s/private/api/prom/configs/alertmanager%s", c.URL.String(), suffix)
   124  	var response *ConfigsResponse
   125  	err := instrument.CollectedRequest(ctx, "GetAlerts", configsRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
   126  		var err error
   127  		response, err = doRequest(endpoint, c.Timeout, c.TLSConfig, since)
   128  		return err
   129  	})
   130  	return response, err
   131  }
   132  
   133  func doRequest(endpoint string, timeout time.Duration, tlsConfig *tls.Config, since userconfig.ID) (*ConfigsResponse, error) {
   134  	req, err := http.NewRequest("GET", endpoint, nil)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	client := &http.Client{Timeout: timeout}
   140  	if tlsConfig != nil {
   141  		client.Transport = &http.Transport{TLSClientConfig: tlsConfig}
   142  	}
   143  
   144  	req.Header.Set("User-Agent", fmt.Sprintf("Cortex/%s", version.Version))
   145  
   146  	resp, err := client.Do(req)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	defer resp.Body.Close()
   151  
   152  	if resp.StatusCode != http.StatusOK {
   153  		return nil, fmt.Errorf("Invalid response from configs server: %v", resp.StatusCode)
   154  	}
   155  
   156  	var config ConfigsResponse
   157  	if err := json.NewDecoder(resp.Body).Decode(&config); err != nil {
   158  		level.Error(util_log.Logger).Log("msg", "configs: couldn't decode JSON body", "err", err)
   159  		return nil, err
   160  	}
   161  
   162  	config.since = since
   163  	return &config, nil
   164  }
   165  
   166  // ConfigsResponse is a response from server for Getuserconfig.
   167  type ConfigsResponse struct {
   168  	// The version since which these configs were changed
   169  	since userconfig.ID
   170  
   171  	// Configs maps user ID to their latest userconfig.View.
   172  	Configs map[string]userconfig.View `json:"configs"`
   173  }
   174  
   175  // GetLatestConfigID returns the last config ID from a set of userconfig.
   176  func (c ConfigsResponse) GetLatestConfigID() userconfig.ID {
   177  	latest := c.since
   178  	for _, config := range c.Configs {
   179  		if config.ID > latest {
   180  			latest = config.ID
   181  		}
   182  	}
   183  	return latest
   184  }