2017-05-16 18:26:07 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2017-05-19 16:16:14 +02:00
|
|
|
"os"
|
2017-05-17 17:29:07 +02:00
|
|
|
"strconv"
|
2017-05-16 18:26:07 +02:00
|
|
|
"strings"
|
|
|
|
|
2018-06-19 10:02:16 +02:00
|
|
|
"github.com/alice-lg/alice-lg/backend/sources"
|
|
|
|
"github.com/alice-lg/alice-lg/backend/sources/birdwatcher"
|
2017-05-17 16:11:52 +02:00
|
|
|
|
|
|
|
"github.com/go-ini/ini"
|
|
|
|
_ "github.com/imdario/mergo"
|
2017-05-16 18:26:07 +02:00
|
|
|
)
|
|
|
|
|
2017-05-17 16:11:52 +02:00
|
|
|
const SOURCE_UNKNOWN = 0
|
|
|
|
const SOURCE_BIRDWATCHER = 1
|
2017-05-16 18:26:07 +02:00
|
|
|
|
2017-05-17 16:11:52 +02:00
|
|
|
type ServerConfig struct {
|
2017-07-04 12:36:48 +02:00
|
|
|
Listen string `ini:"listen_http"`
|
|
|
|
EnablePrefixLookup bool `ini:"enable_prefix_lookup"`
|
2017-05-16 18:26:07 +02:00
|
|
|
}
|
|
|
|
|
2017-05-17 17:29:07 +02:00
|
|
|
type RejectionsConfig struct {
|
|
|
|
Asn int `ini:"asn"`
|
|
|
|
RejectId int `ini:"reject_id"`
|
|
|
|
|
|
|
|
Reasons map[int]string
|
|
|
|
}
|
|
|
|
|
|
|
|
type NoexportsConfig struct {
|
2017-05-18 15:23:36 +02:00
|
|
|
Asn int `ini:"asn"`
|
|
|
|
NoexportId int `ini:"noexport_id"`
|
2017-05-17 17:29:07 +02:00
|
|
|
|
|
|
|
Reasons map[int]string
|
|
|
|
}
|
|
|
|
|
|
|
|
type UiConfig struct {
|
|
|
|
RoutesColumns map[string]string
|
|
|
|
|
|
|
|
RoutesRejections RejectionsConfig
|
|
|
|
RoutesNoexports NoexportsConfig
|
2018-06-20 11:07:07 +02:00
|
|
|
|
|
|
|
Theme ThemeConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
type ThemeConfig struct {
|
2018-06-20 19:13:28 +02:00
|
|
|
Path string `ini:"path"`
|
2018-06-25 23:13:05 +02:00
|
|
|
BasePath string `ini:"url_base"` // Optional, default: /theme
|
2017-05-17 17:29:07 +02:00
|
|
|
}
|
|
|
|
|
2017-05-16 18:26:07 +02:00
|
|
|
type SourceConfig struct {
|
2017-06-19 15:27:11 +02:00
|
|
|
Id int
|
2017-05-16 18:26:07 +02:00
|
|
|
Name string
|
2017-05-17 16:11:52 +02:00
|
|
|
Type int
|
|
|
|
|
|
|
|
// Source configurations
|
|
|
|
Birdwatcher birdwatcher.Config
|
2017-05-16 18:26:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type Config struct {
|
2017-05-17 16:11:52 +02:00
|
|
|
Server ServerConfig
|
2017-05-17 17:29:07 +02:00
|
|
|
Ui UiConfig
|
2017-05-16 18:26:07 +02:00
|
|
|
Sources []SourceConfig
|
2017-05-19 16:16:14 +02:00
|
|
|
File string
|
2017-06-23 18:01:49 +02:00
|
|
|
|
|
|
|
instances map[SourceConfig]sources.Source
|
2017-05-16 18:26:07 +02:00
|
|
|
}
|
|
|
|
|
2017-05-17 16:11:52 +02:00
|
|
|
// Get sources keys form ini
|
|
|
|
func getSourcesKeys(config *ini.File) []string {
|
|
|
|
sources := []string{}
|
|
|
|
sections := config.SectionStrings()
|
|
|
|
for _, section := range sections {
|
|
|
|
if strings.HasPrefix(section, "source") {
|
|
|
|
sources = append(sources, section)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sources
|
|
|
|
}
|
|
|
|
|
|
|
|
func isSourceBase(section *ini.Section) bool {
|
|
|
|
return len(strings.Split(section.Name(), ".")) == 2
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get backend configuration type
|
|
|
|
func getBackendType(section *ini.Section) int {
|
|
|
|
name := section.Name()
|
|
|
|
if strings.HasSuffix(name, "birdwatcher") {
|
|
|
|
return SOURCE_BIRDWATCHER
|
|
|
|
}
|
|
|
|
|
|
|
|
return SOURCE_UNKNOWN
|
|
|
|
}
|
|
|
|
|
2017-05-17 17:29:07 +02:00
|
|
|
// Get UI config: Routes Columns
|
|
|
|
// The columns displayed in the frontend
|
|
|
|
func getRoutesColumns(config *ini.File) (map[string]string, error) {
|
|
|
|
columns := make(map[string]string)
|
|
|
|
section := config.Section("routes_columns")
|
|
|
|
keys := section.Keys()
|
|
|
|
|
|
|
|
for _, key := range keys {
|
|
|
|
columns[key.Name()] = section.Key(key.Name()).MustString("")
|
|
|
|
}
|
|
|
|
|
|
|
|
return columns, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get UI config: Get rejections
|
|
|
|
func getRoutesRejections(config *ini.File) (RejectionsConfig, error) {
|
|
|
|
reasons := make(map[int]string)
|
|
|
|
baseConfig := config.Section("rejection")
|
|
|
|
reasonsConfig := config.Section("rejection_reasons")
|
|
|
|
|
|
|
|
// Map base configuration
|
|
|
|
rejectionsConfig := RejectionsConfig{}
|
|
|
|
baseConfig.MapTo(&rejectionsConfig)
|
|
|
|
|
|
|
|
// Map reasons
|
|
|
|
keys := reasonsConfig.Keys()
|
|
|
|
for _, key := range keys {
|
|
|
|
id, err := strconv.Atoi(key.Name())
|
|
|
|
if err != nil {
|
|
|
|
return rejectionsConfig, err
|
|
|
|
}
|
|
|
|
reasons[id] = reasonsConfig.Key(key.Name()).MustString("")
|
|
|
|
}
|
|
|
|
|
|
|
|
rejectionsConfig.Reasons = reasons
|
|
|
|
|
|
|
|
return rejectionsConfig, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get UI config: Get no export config
|
|
|
|
func getRoutesNoexports(config *ini.File) (NoexportsConfig, error) {
|
|
|
|
reasons := make(map[int]string)
|
|
|
|
baseConfig := config.Section("noexport")
|
|
|
|
reasonsConfig := config.Section("noexport_reasons")
|
|
|
|
|
|
|
|
// Map base configuration
|
|
|
|
noexportsConfig := NoexportsConfig{}
|
|
|
|
baseConfig.MapTo(&noexportsConfig)
|
|
|
|
|
|
|
|
// Map reasons for missing export
|
|
|
|
keys := reasonsConfig.Keys()
|
|
|
|
for _, key := range keys {
|
|
|
|
id, err := strconv.Atoi(key.Name())
|
|
|
|
if err != nil {
|
|
|
|
return noexportsConfig, err
|
|
|
|
}
|
|
|
|
reasons[id] = reasonsConfig.Key(key.Name()).MustString("")
|
|
|
|
}
|
|
|
|
|
|
|
|
noexportsConfig.Reasons = reasons
|
|
|
|
|
|
|
|
return noexportsConfig, nil
|
|
|
|
}
|
|
|
|
|
2018-06-20 11:07:07 +02:00
|
|
|
// Get UI config: Theme settings
|
|
|
|
func getThemeConfig(config *ini.File) ThemeConfig {
|
|
|
|
baseConfig := config.Section("theme")
|
|
|
|
|
|
|
|
themeConfig := ThemeConfig{}
|
|
|
|
baseConfig.MapTo(&themeConfig)
|
|
|
|
|
2018-06-20 19:13:28 +02:00
|
|
|
if themeConfig.BasePath == "" {
|
|
|
|
themeConfig.BasePath = "/theme"
|
|
|
|
}
|
|
|
|
|
2018-06-20 11:07:07 +02:00
|
|
|
return themeConfig
|
|
|
|
}
|
|
|
|
|
2017-05-17 17:29:07 +02:00
|
|
|
// Get the UI configuration from the config file
|
|
|
|
func getUiConfig(config *ini.File) (UiConfig, error) {
|
|
|
|
uiConfig := UiConfig{}
|
|
|
|
|
|
|
|
// Get route columns
|
|
|
|
routesColumns, err := getRoutesColumns(config)
|
|
|
|
if err != nil {
|
|
|
|
return uiConfig, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get rejections and reasons
|
|
|
|
rejections, err := getRoutesRejections(config)
|
|
|
|
if err != nil {
|
|
|
|
return uiConfig, err
|
|
|
|
}
|
|
|
|
|
|
|
|
noexports, err := getRoutesNoexports(config)
|
|
|
|
if err != nil {
|
|
|
|
return uiConfig, err
|
|
|
|
}
|
|
|
|
|
2018-06-20 11:07:07 +02:00
|
|
|
// Theme configuration: Theming is optional, if no settings
|
|
|
|
// are found, it will be ignored
|
|
|
|
themeConfig := getThemeConfig(config)
|
|
|
|
|
2017-05-17 17:29:07 +02:00
|
|
|
// Make config
|
|
|
|
uiConfig = UiConfig{
|
|
|
|
RoutesColumns: routesColumns,
|
|
|
|
RoutesRejections: rejections,
|
|
|
|
RoutesNoexports: noexports,
|
2018-06-20 11:07:07 +02:00
|
|
|
|
|
|
|
Theme: themeConfig,
|
2017-05-17 17:29:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return uiConfig, nil
|
|
|
|
}
|
|
|
|
|
2017-05-17 16:11:52 +02:00
|
|
|
func getSources(config *ini.File) ([]SourceConfig, error) {
|
|
|
|
sources := []SourceConfig{}
|
|
|
|
|
|
|
|
sourceSections := config.ChildSections("source")
|
2017-06-26 16:41:50 +02:00
|
|
|
sourceId := 0
|
|
|
|
for _, section := range sourceSections {
|
2017-05-17 16:11:52 +02:00
|
|
|
if !isSourceBase(section) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to get child configs and determine
|
|
|
|
// Source type
|
|
|
|
sourceConfigSections := section.ChildSections()
|
|
|
|
if len(sourceConfigSections) == 0 {
|
|
|
|
// This source has no configured backend
|
|
|
|
return sources, fmt.Errorf("%s has no backend configuration", section.Name())
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(sourceConfigSections) > 1 {
|
|
|
|
// The source is ambiguous
|
|
|
|
return sources, fmt.Errorf("%s has ambigous backends", section.Name())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configure backend
|
|
|
|
backendConfig := sourceConfigSections[0]
|
|
|
|
backendType := getBackendType(backendConfig)
|
|
|
|
|
|
|
|
if backendType == SOURCE_UNKNOWN {
|
|
|
|
return sources, fmt.Errorf("%s has an unsupported backend", section.Name())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make config
|
|
|
|
config := SourceConfig{
|
2017-06-26 16:41:50 +02:00
|
|
|
Id: sourceId,
|
2017-05-18 17:35:12 +02:00
|
|
|
Name: section.Key("name").MustString("Unknown Source"),
|
2017-05-17 16:11:52 +02:00
|
|
|
Type: backendType,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set backend
|
|
|
|
switch backendType {
|
|
|
|
case SOURCE_BIRDWATCHER:
|
2017-11-20 12:05:55 +01:00
|
|
|
c := birdwatcher.Config{
|
|
|
|
Id: config.Id,
|
|
|
|
Name: config.Name,
|
|
|
|
|
2017-10-19 14:16:03 +02:00
|
|
|
ServerTime: "2006-01-02T15:04:05.999999999Z07:00",
|
|
|
|
ServerTimeShort: "2006-01-02",
|
|
|
|
ServerTimeExt: "Mon, 02 Jan 2006 15:04:05 -0700",
|
|
|
|
}
|
2017-11-20 12:05:55 +01:00
|
|
|
backendConfig.MapTo(&c)
|
|
|
|
config.Birdwatcher = c
|
2017-05-17 16:11:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add to list of sources
|
|
|
|
sources = append(sources, config)
|
2017-06-26 16:41:50 +02:00
|
|
|
|
|
|
|
sourceId += 1
|
2017-05-17 16:11:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return sources, nil
|
|
|
|
}
|
|
|
|
|
2017-05-16 18:26:07 +02:00
|
|
|
// Try to load configfiles as specified in the files
|
|
|
|
// list. For example:
|
|
|
|
//
|
|
|
|
// ./etc/alicelg/alice.conf
|
|
|
|
// /etc/alicelg/alice.conf
|
|
|
|
// ./etc/alicelg/alice.local.conf
|
|
|
|
//
|
2017-05-19 16:16:14 +02:00
|
|
|
func loadConfig(file string) (*Config, error) {
|
|
|
|
|
|
|
|
// Try to get config file, fallback to alternatives
|
|
|
|
file, err := getConfigFile(file)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
parsedConfig, err := ini.LooseLoad(file)
|
2017-05-17 16:11:52 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map sections
|
|
|
|
server := ServerConfig{}
|
|
|
|
parsedConfig.Section("server").MapTo(&server)
|
|
|
|
|
|
|
|
// Get all sources
|
|
|
|
sources, err := getSources(parsedConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-05-16 18:26:07 +02:00
|
|
|
}
|
|
|
|
|
2017-05-17 17:29:07 +02:00
|
|
|
// Get UI configurations
|
|
|
|
ui, err := getUiConfig(parsedConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-05-17 16:11:52 +02:00
|
|
|
config := &Config{
|
|
|
|
Server: server,
|
2017-05-17 17:29:07 +02:00
|
|
|
Ui: ui,
|
2017-05-17 16:11:52 +02:00
|
|
|
Sources: sources,
|
2017-05-19 16:16:14 +02:00
|
|
|
File: file,
|
2017-05-16 18:26:07 +02:00
|
|
|
}
|
|
|
|
|
2017-05-17 16:11:52 +02:00
|
|
|
return config, nil
|
2017-05-16 18:26:07 +02:00
|
|
|
}
|
|
|
|
|
2017-05-18 18:10:15 +02:00
|
|
|
// Get source instance from config
|
|
|
|
func (source SourceConfig) getInstance() sources.Source {
|
|
|
|
switch source.Type {
|
|
|
|
case SOURCE_BIRDWATCHER:
|
|
|
|
return birdwatcher.NewBirdwatcher(source.Birdwatcher)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2017-05-19 16:16:14 +02:00
|
|
|
|
|
|
|
// Get configuration file with fallbacks
|
|
|
|
func getConfigFile(filename string) (string, error) {
|
|
|
|
// Check if requested file is present
|
|
|
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
|
|
|
// Fall back to local filename
|
|
|
|
filename = ".." + filename
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
|
|
|
filename = strings.Replace(filename, ".conf", ".local.conf", 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
|
|
|
return "not_found", fmt.Errorf("could not find any configuration file")
|
|
|
|
}
|
|
|
|
|
|
|
|
return filename, nil
|
|
|
|
}
|