Skip to content

Commit

Permalink
Prior config refactoring before adding prometheus config (#511)
Browse files Browse the repository at this point in the history
- Merged the different Config structs (from handler.Config,
  server.Config and loki.Config) in a dedicated config package
- Add names to table-based tests
- A few linter fixes (e.g. space after comments)
  • Loading branch information
jotak authored Apr 16, 2024
1 parent 9cc4ec8 commit d804edb
Show file tree
Hide file tree
Showing 22 changed files with 384 additions and 421 deletions.
105 changes: 8 additions & 97 deletions cmd/plugin-backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,11 @@ package main
import (
"flag"
"fmt"
"net/url"
"os"
"strings"
"time"

"github.com/sirupsen/logrus"

"github.com/netobserv/network-observability-console-plugin/pkg/handler"
"github.com/netobserv/network-observability-console-plugin/pkg/kubernetes/auth"
"github.com/netobserv/network-observability-console-plugin/pkg/kubernetes/client"
"github.com/netobserv/network-observability-console-plugin/pkg/loki"
"github.com/netobserv/network-observability-console-plugin/pkg/config"
"github.com/netobserv/network-observability-console-plugin/pkg/server"
)

Expand Down Expand Up @@ -45,104 +39,21 @@ func main() {
logrus.SetLevel(lvl)
log.Infof("Starting %s at log level %s", appVersion, *logLevel)

config, err := handler.ReadConfigFile(buildVersion, buildDate, *configPath)
cfg, err := config.ReadFile(buildVersion, buildDate, *configPath)
if err != nil {
log.WithError(err).Fatal("error reading config file")
}

// check config required fields
var configErrors []string
if len(config.Loki.Labels) == 0 {
configErrors = append(configErrors, "labels cannot be empty")
}

// parse config urls
var lURL, lStatusURL *url.URL
if len(config.Loki.URL) == 0 {
configErrors = append(configErrors, "url cannot be empty")
} else {
lURL, err = url.Parse(config.Loki.URL)
if err != nil {
configErrors = append(configErrors, "wrong Loki URL")
}
}
if len(config.Loki.StatusURL) > 0 {
lStatusURL, err = url.Parse(config.Loki.StatusURL)
if err != nil {
configErrors = append(configErrors, "wrong Loki status URL")
}
} else {
lStatusURL = lURL
}

// parse config timeout
ltimeout, err := time.ParseDuration(config.Loki.Timeout)
if err != nil {
configErrors = append(configErrors, "wrong Loki timeout")
}

// parse config auth
var checkType auth.CheckType
if config.Loki.AuthCheck == "auto" {
if config.Loki.ForwardUserToken {
// FORWARD lokiAuth mode
checkType = auth.CheckAuthenticated
} else {
// HOST or DISABLED lokiAuth mode
checkType = auth.CheckAdmin
}
log.Info(fmt.Sprintf("auth-check 'auto' resolved to '%s'", checkType))
} else {
checkType = auth.CheckType(config.Loki.AuthCheck)
}
if checkType == auth.CheckNone {
log.Warn("INSECURE: auth checker is disabled")
}
checker, err := auth.NewChecker(checkType, client.NewInCluster)
checker, err := cfg.GetAuthChecker()
if err != nil {
configErrors = append(configErrors, "auth checker error")
}

// crash on config errors
if len(configErrors) > 0 {
configErrors = append([]string{fmt.Sprintf("Config file has %d errors:\n", len(configErrors))}, configErrors...)
log.Fatal(strings.Join(configErrors, "\n - "))
log.WithError(err).Fatal("auth checker error")
}

go server.StartMetrics(&server.MetricsConfig{
Port: config.Server.MetricsPort,
CertPath: config.Server.CertPath,
KeyPath: config.Server.KeyPath,
Port: cfg.Server.MetricsPort,
CertPath: cfg.Server.CertPath,
KeyPath: cfg.Server.KeyPath,
})

server.Start(&server.Config{
BuildVersion: buildVersion,
BuildDate: buildDate,
Port: config.Server.Port,
CertPath: config.Server.CertPath,
KeyPath: config.Server.KeyPath,
CORSAllowOrigin: config.Server.CORSOrigin,
CORSAllowMethods: config.Server.CORSMethods,
CORSAllowHeaders: config.Server.CORSHeaders,
CORSMaxAge: config.Server.CORSMaxAge,
ConfigPath: *configPath,
Loki: loki.NewConfig(
lURL,
lStatusURL,
ltimeout,
config.Loki.TenantID,
config.Loki.TokenPath,
config.Loki.ForwardUserToken,
config.Loki.SkipTLS,
config.Loki.CAPath,
config.Loki.StatusSkipTLS,
config.Loki.CAPath,
config.Loki.StatusUserCertPath,
config.Loki.StatusUserKeyPath,
config.Loki.UseMocks,
config.Loki.Labels,
config.Frontend.Deduper.Mark,
config.Frontend.Deduper.Merge,
),
}, checker)
server.Start(cfg, checker)
}
114 changes: 72 additions & 42 deletions pkg/handler/config.go → pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
package handler
package config

import (
"net/http"
"fmt"
"net/url"
"os"
"strings"
"time"

"github.com/netobserv/network-observability-console-plugin/pkg/kubernetes/auth"
"github.com/netobserv/network-observability-console-plugin/pkg/kubernetes/client"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)

var (
log = logrus.WithField("module", "config")
)

type Server struct {
Port int `yaml:"port,omitempty" json:"port,omitempty"`
MetricsPort int `yaml:"metricsPort,omitempty" json:"metricsPort,omitempty"`
Expand All @@ -18,25 +28,6 @@ type Server struct {
CORSMaxAge string `yaml:"corsMaxAge,omitempty" json:"corsMaxAge,omitempty"`
}

type Loki struct {
URL string `yaml:"url" json:"url"`
Labels []string `yaml:"labels" json:"labels"`

StatusURL string `yaml:"statusUrl,omitempty" json:"statusUrl,omitempty"`
Timeout string `yaml:"timeout,omitempty" json:"timeout,omitempty"`
TenantID string `yaml:"tenantID,omitempty" json:"tenantID,omitempty"`
TokenPath string `yaml:"tokenPath,omitempty" json:"tokenPath,omitempty"`
SkipTLS bool `yaml:"skipTls,omitempty" json:"skipTls,omitempty"`
CAPath string `yaml:"caPath,omitempty" json:"caPath,omitempty"`
StatusSkipTLS bool `yaml:"statusSkipTls,omitempty" json:"statusSkipTls,omitempty"`
StatusCAPath string `yaml:"statusCaPath,omitempty" json:"statusCaPath,omitempty"`
StatusUserCertPath string `yaml:"statusUserCertPath,omitempty" json:"statusUserCertPath,omitempty"`
StatusUserKeyPath string `yaml:"statusUserKeyPath,omitempty" json:"statusUserKeyPath,omitempty"`
UseMocks bool `yaml:"useMocks,omitempty" json:"useMocks,omitempty"`
ForwardUserToken bool `yaml:"forwardUserToken,omitempty" json:"forwardUserToken,omitempty"`
AuthCheck string `yaml:"authCheck,omitempty" json:"authCheck,omitempty"`
}

type PortNaming struct {
Enable bool `yaml:"enable" json:"enable"`
PortNames map[string]string `yaml:"portNames" json:"portNames"`
Expand Down Expand Up @@ -110,21 +101,22 @@ type Frontend struct {
type Config struct {
Loki Loki `yaml:"loki" json:"loki"`
Frontend Frontend `yaml:"frontend" json:"frontend"`

Server Server `yaml:"server,omitempty" json:"server,omitempty"`
Server Server `yaml:"server,omitempty" json:"server,omitempty"`
Path string `yaml:"-" json:"-"`
}

func ReadConfigFile(version, date, filename string) (*Config, error) {
//set default vales
func ReadFile(version, date, filename string) (*Config, error) {
// set default values
cfg := Config{
Path: filename,
Server: Server{
Port: 9001,
MetricsPort: 9002,
CORSOrigin: "*",
CORSHeaders: "Origin, X-Requested-With, Content-Type, Accept",
},
Loki: Loki{
Timeout: "30s",
Timeout: Duration{Duration: 30 * time.Second},
AuthCheck: "auto",
},
Frontend: Frontend{
Expand All @@ -140,10 +132,9 @@ func ReadConfigFile(version, date, filename string) (*Config, error) {
Filters: []Filter{},
QuickFilters: []QuickFilter{},
Features: []string{},
// TODO: update these defaults when operator will move to merge mode
Deduper: Deduper{
Mark: true,
Merge: false,
Mark: false,
Merge: true,
},
Fields: []FieldConfig{
{Name: "TimeFlowEndMs", Type: "number"},
Expand All @@ -160,24 +151,63 @@ func ReadConfigFile(version, date, filename string) (*Config, error) {
return nil, err
}
err = yaml.Unmarshal(yamlFile, &cfg)
return &cfg, err
if err != nil {
return nil, err
}

cfg.Validate()

return &cfg, nil
}

func GetFrontendConfig(version, date, filename string) func(w http.ResponseWriter, r *http.Request) {
config, err := ReadConfigFile(version, date, filename)
if err != nil {
hlog.Errorf("Could not read config file: %v", err)
func (c *Config) Validate() {
var configErrors []string

// check config required fields
if len(c.Loki.Labels) == 0 {
configErrors = append(configErrors, "labels cannot be empty")
}
return func(w http.ResponseWriter, r *http.Request) {

// parse config urls
if len(c.Loki.URL) == 0 {
configErrors = append(configErrors, "url cannot be empty")
} else {
_, err := url.Parse(c.Loki.URL)
if err != nil {
configErrors = append(configErrors, "wrong Loki URL")
}
}
if len(c.Loki.StatusURL) > 0 {
_, err := url.Parse(c.Loki.StatusURL)
if err != nil {
config, err = ReadConfigFile(version, date, filename)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
} else {
writeJSON(w, http.StatusOK, config.Frontend)
}
configErrors = append(configErrors, "wrong Loki status URL")
}
}

// crash on config errors
if len(configErrors) > 0 {
configErrors = append([]string{fmt.Sprintf("Config file has %d errors:\n", len(configErrors))}, configErrors...)
log.Fatal(strings.Join(configErrors, "\n - "))
}
}

func (c *Config) GetAuthChecker() (auth.Checker, error) {
// parse config auth
var checkType auth.CheckType
if c.Loki.AuthCheck == "auto" {
if c.Loki.ForwardUserToken {
// FORWARD lokiAuth mode
checkType = auth.CheckAuthenticated
} else {
writeJSON(w, http.StatusOK, config.Frontend)
// HOST or DISABLED lokiAuth mode
checkType = auth.CheckAdmin
}
log.Info(fmt.Sprintf("auth-check 'auto' resolved to '%s'", checkType))
} else {
checkType = auth.CheckType(c.Loki.AuthCheck)
}
if checkType == auth.CheckNone {
log.Warn("INSECURE: auth checker is disabled")
}
return auth.NewChecker(checkType, client.NewInCluster)
}
56 changes: 56 additions & 0 deletions pkg/config/duration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package config

// TODO: move this file in a new netobserv-libs repo ? (Same in FLP)

import (
"encoding/json"
"fmt"
"time"
)

// Duration is a wrapper of time.Duration that allows json marshaling.
type Duration struct {
time.Duration
}

func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}

func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
d.Duration = time.Duration(value)
return nil
case string:
var err error
d.Duration, err = time.ParseDuration(value)
if err != nil {
return err
}
return nil
default:
return fmt.Errorf("invalid duration %v", value)
}
}

func (d Duration) MarshalYAML() (interface{}, error) {
return d.String(), nil
}

func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
var durationStr string
err := unmarshal(&durationStr)
if err != nil {
return err
}
d.Duration, err = time.ParseDuration(durationStr)
if err != nil {
return err
}
return nil
}
37 changes: 37 additions & 0 deletions pkg/config/loki.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package config

import "github.com/netobserv/network-observability-console-plugin/pkg/utils"

type Loki struct {
URL string `yaml:"url" json:"url"`
Labels []string `yaml:"labels" json:"labels"`
StatusURL string `yaml:"statusUrl,omitempty" json:"statusUrl,omitempty"`
Timeout Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
TenantID string `yaml:"tenantID,omitempty" json:"tenantID,omitempty"`
TokenPath string `yaml:"tokenPath,omitempty" json:"tokenPath,omitempty"`
SkipTLS bool `yaml:"skipTls,omitempty" json:"skipTls,omitempty"`
CAPath string `yaml:"caPath,omitempty" json:"caPath,omitempty"`
StatusSkipTLS bool `yaml:"statusSkipTls,omitempty" json:"statusSkipTls,omitempty"`
StatusCAPath string `yaml:"statusCaPath,omitempty" json:"statusCaPath,omitempty"`
StatusUserCertPath string `yaml:"statusUserCertPath,omitempty" json:"statusUserCertPath,omitempty"`
StatusUserKeyPath string `yaml:"statusUserKeyPath,omitempty" json:"statusUserKeyPath,omitempty"`
UseMocks bool `yaml:"useMocks,omitempty" json:"useMocks,omitempty"`
ForwardUserToken bool `yaml:"forwardUserToken,omitempty" json:"forwardUserToken,omitempty"`
AuthCheck string `yaml:"authCheck,omitempty" json:"authCheck,omitempty"`
labelsMap map[string]struct{}
}

func (l *Loki) GetStatusURL() string {
if l.StatusURL != "" {
return l.StatusURL
}
return l.URL
}

func (l *Loki) IsLabel(key string) bool {
if l.labelsMap == nil {
l.labelsMap = utils.GetMapInterface(l.Labels)
}
_, isLabel := l.labelsMap[key]
return isLabel
}
Loading

0 comments on commit d804edb

Please sign in to comment.