<commit_before>// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"github.com/conformal/btcdb"
	_ "github.com/conformal/btcdb/ldb"
	"github.com/conformal/btcutil"
	"github.com/conformal/btcwire"
	"github.com/conformal/go-flags"
	"net"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

const (
	defaultConfigFilename = "btcd.conf"
	defaultDataDirname    = "data"
	defaultLogLevel       = "info"
	defaultBtcnet         = btcwire.MainNet
	defaultMaxPeers       = 125
	defaultBanDuration    = time.Hour * 24
	defaultVerifyEnabled  = false
	defaultDbType         = "leveldb"
)

var (
	btcdHomeDir       = btcutil.AppDataDir("btcd", false)
	defaultConfigFile = filepath.Join(btcdHomeDir, defaultConfigFilename)
	defaultDataDir    = filepath.Join(btcdHomeDir, defaultDataDirname)
	defaultListener   = net.JoinHostPort("", netParams(defaultBtcnet).listenPort)
	knownDbTypes      = btcdb.SupportedDBs()
)

// config defines the configuration options for btcd.
//
// See loadConfig for details on the configuration load process.
type config struct {
	ShowVersion        bool          `short:"V" long:"version" description:"Display version information and exit"`
	ConfigFile         string        `short:"C" long:"configfile" description:"Path to configuration file"`
	DataDir            string        `short:"b" long:"datadir" description:"Directory to store data"`
	AddPeers           []string      `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
	ConnectPeers       []string      `long:"connect" description:"Connect only to the specified peers at startup"`
	DisableListen      bool          `long:"nolisten" description:"Disable listening for incoming connections -- NOTE: Listening is automatically disabled if the --connect option is used or if the --proxy option is used without the --tor option"`
	Listeners          []string      `long:"listen" description:"Listen for connections on this interface/port (default all interfaces port: 8333, testnet: 18333)"`
	MaxPeers           int           `long:"maxpeers" description:"Max number of inbound and outbound peers"`
	BanDuration        time.Duration `long:"banduration" description:"How long to ban misbehaving peers.  Valid time units are {s, m, h}.  Minimum 1 second"`
	RPCUser            string        `short:"u" long:"rpcuser" description:"Username for RPC connections"`
	RPCPass            string        `short:"P" long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
	RPCPort            string        `short:"r" long:"rpcport" description:"Listen for JSON/RPC messages on this port"`
	DisableRPC         bool          `long:"norpc" description:"Disable built-in RPC server -- NOTE: The RPC server is disabled by default if no rpcuser/rpcpass is specified"`
	DisableDNSSeed     bool          `long:"nodnsseed" description:"Disable DNS seeding for peers"`
	Proxy              string        `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
	ProxyUser          string        `long:"proxyuser" description:"Username for proxy server"`
	ProxyPass          string        `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
	UseTor             bool          `long:"tor" description:"Specifies the proxy server used is a Tor node"`
	TestNet3           bool          `long:"testnet" description:"Use the test network"`
	RegressionTest     bool          `long:"regtest" description:"Use the regression test network"`
	DisableCheckpoints bool          `long:"nocheckpoints" description:"Disable built-in checkpoints.  Don't do this unless you know what you're doing."`
	DbType             string        `long:"dbtype" description:"Database backend to use for the Block Chain"`
	Profile            string        `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
	CpuProfile         string        `long:"cpuprofile" description:"Write CPU profile to the specified file"`
	DebugLevel         string        `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
}

// cleanAndExpandPath expands environement variables and leading ~ in the
// passed path, cleans the result, and returns it.
func cleanAndExpandPath(path string) string {
	// Expand initial ~ to OS specific home directory.
	if strings.HasPrefix(path, "~") {
		homeDir := filepath.Dir(btcdHomeDir)
		path = strings.Replace(path, "~", homeDir, 1)
	}

	// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
	// but they variables can still be expanded via POSIX-style $VARIABLE.
	return filepath.Clean(os.ExpandEnv(path))
}

// validLogLevel returns whether or not logLevel is a valid debug log level.
func validLogLevel(logLevel string) bool {
	switch logLevel {
	case "trace":
		fallthrough
	case "debug":
		fallthrough
	case "info":
		fallthrough
	case "warn":
		fallthrough
	case "error":
		fallthrough
	case "critical":
		return true
	}
	return false
}

// validDbType returns whether or not dbType is a supported database type.
func validDbType(dbType string) bool {
	for _, knownType := range knownDbTypes {
		if dbType == knownType {
			return true
		}
	}

	return false
}

// removeDuplicateAddresses returns a new slice with all duplicate entries in
// addrs removed.
func removeDuplicateAddresses(addrs []string) []string {
	result := make([]string, 0)
	seen := map[string]bool{}
	for _, val := range addrs {
		if _, ok := seen[val]; !ok {
			result = append(result, val)
			seen[val] = true
		}
	}
	return result
}

// normalizeAddress returns addr with the passed default port appended if
// there is not already a port specified.
func normalizeAddress(addr, defaultPort string) string {
	_, _, err := net.SplitHostPort(addr)
	if err != nil {
		return net.JoinHostPort(addr, defaultPort)
	}
	return addr
}

// normalizeAddresses returns a new slice with all the passed peer addresses
// normalized with the given default port, and all duplicates removed.
func normalizeAddresses(addrs []string, defaultPort string) []string {
	for i, addr := range addrs {
		addrs[i] = normalizeAddress(addr, defaultPort)
	}

	return removeDuplicateAddresses(addrs)
}

// cleanListenAddresses returns a new slice with all the passed peer addresses
// normalized and duplicates removed.
func cleanListenAddresses(addrs []string) []string {
	return normalizeAddresses(addrs, activeNetParams.listenPort)
}

// cleanPeerAddresses returns a new slice with all the passed peer addresses
// normalized and duplicates removed.
func cleanPeerAddresses(addrs []string) []string {
	return normalizeAddresses(addrs, activeNetParams.peerPort)
}

// updateConfigWithActiveParams update the passed config with parameters
// from the active net params if the relevant options in the passed config
// object are the default so options specified by the user on the command line
// are not overridden.
func updateConfigWithActiveParams(cfg *config) {
	// Even though there should only be one default, a duplicate might
	// have been specified via the config file or CLI options.  So, make
	// sure to update all default entries rather than only the first one
	// found.  Duplicates are removed later.

	if cfg.RPCPort == netParams(defaultBtcnet).rpcPort {
		cfg.RPCPort = activeNetParams.rpcPort
	}
}

// filesExists reports whether the named file or directory exists.
func fileExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// loadConfig initializes and parses the config using a config file and command
// line options.
//
// The configuration proceeds as follows:
// 	1) Start with a default config with sane settings
// 	2) Pre-parse the command line to check for an alternative config file
// 	3) Load configuration file overwriting defaults with any specified options
// 	4) Parse CLI options and overwrite/add any specified options
//
// The above results in btcd functioning properly without any config settings
// while still allowing the user to override settings with config files and
// command line options.  Command line options always take precedence.
func loadConfig() (*config, []string, error) {
	// Default config.
	cfg := config{
		DebugLevel:  defaultLogLevel,
		RPCPort:     netParams(defaultBtcnet).rpcPort,
		MaxPeers:    defaultMaxPeers,
		BanDuration: defaultBanDuration,
		ConfigFile:  defaultConfigFile,
		DataDir:     defaultDataDir,
		DbType:      defaultDbType,
	}

	// Pre-parse the command line options to see if an alternative config
	// file or the version flag was specified.  Any errors can be ignored
	// here since they will be caught be the final parse below.
	preCfg := cfg
	preParser := flags.NewParser(&preCfg, flags.None)
	preParser.Parse()

	// Show the version and exit if the version flag was specified.
	if preCfg.ShowVersion {
		appName := filepath.Base(os.Args[0])
		appName = strings.TrimSuffix(appName, filepath.Ext(appName))
		fmt.Println(appName, "version", version())
		os.Exit(0)
	}

	// Load additional config from file.
	parser := flags.NewParser(&cfg, flags.Default)
	err := parser.ParseIniFile(preCfg.ConfigFile)
	if err != nil {
		if _, ok := err.(*os.PathError); !ok {
			fmt.Fprintln(os.Stderr, err)
			parser.WriteHelp(os.Stderr)
			return nil, nil, err
		}
		log.Warnf("%v", err)
	}

	// Don't add peers from the config file when in regression test mode.
	if preCfg.RegressionTest && len(cfg.AddPeers) > 0 {
		cfg.AddPeers = nil
	}

	// Parse command line options again to ensure they take precedence.
	remainingArgs, err := parser.Parse()
	if err != nil {
		if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
			parser.WriteHelp(os.Stderr)
		}
		return nil, nil, err
	}

	// The two test networks can't be selected simultaneously.
	if cfg.TestNet3 && cfg.RegressionTest {
		str := "%s: The testnet and regtest params can't be used " +
			"together -- choose one of the two"
		err := fmt.Errorf(str, "loadConfig")
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Choose the active network params based on the testnet and regression
	// test net flags.
	if cfg.TestNet3 {
		activeNetParams = netParams(btcwire.TestNet3)
	} else if cfg.RegressionTest {
		activeNetParams = netParams(btcwire.TestNet)
	}
	updateConfigWithActiveParams(&cfg)

	// Validate debug log level.
	if !validLogLevel(cfg.DebugLevel) {
		str := "%s: The specified debug level [%v] is invalid"
		err := fmt.Errorf(str, "loadConfig", cfg.DebugLevel)
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Validate database type.
	if !validDbType(cfg.DbType) {
		str := "%s: The specified database type [%v] is invalid -- " +
			"supported types %v"
		err := fmt.Errorf(str, "loadConfig", cfg.DbType, knownDbTypes)
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Validate profile port number
	if cfg.Profile != "" {
		profilePort, err := strconv.Atoi(cfg.Profile)
		if err != nil || profilePort < 1024 || profilePort > 65535 {
			str := "%s: The profile port must be between 1024 and 65535"
			err := fmt.Errorf(str, "loadConfig")
			fmt.Fprintln(os.Stderr, err)
			parser.WriteHelp(os.Stderr)
			return nil, nil, err
		}
	}

	// Append the network type to the data directory so it is "namespaced"
	// per network.  In addition to the block database, there are other
	// pieces of data that are saved to disk such as address manager state.
	// All data is specific to a network, so namespacing the data directory
	// means each individual piece of serialized data does not have to
	// worry about changing names per network and such.
	cfg.DataDir = cleanAndExpandPath(cfg.DataDir)
	cfg.DataDir = filepath.Join(cfg.DataDir, activeNetParams.netName)

	// Don't allow ban durations that are too short.
	if cfg.BanDuration < time.Duration(time.Second) {
		str := "%s: The banduration option may not be less than 1s -- parsed [%v]"
		err := fmt.Errorf(str, "loadConfig", cfg.BanDuration)
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --addPeer and --connect do not mix.
	if len(cfg.AddPeers) > 0 && len(cfg.ConnectPeers) > 0 {
		str := "%s: the --addpeer and --connect options can not be " +
			"mixed"
		err := fmt.Errorf(str, "loadConfig")
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --tor requires --proxy to be set.
	if cfg.UseTor && cfg.Proxy == "" {
		str := "%s: the --tor option requires --proxy to be set"
		err := fmt.Errorf(str, "loadConfig")
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --proxy or --connect without --listen disables listening.
	if (cfg.Proxy != "" || len(cfg.ConnectPeers) > 0) &&
		len(cfg.Listeners) == 0 {
		cfg.DisableListen = true
	}

	// Connect means no DNS seeding.
	if len(cfg.ConnectPeers) > 0 {
		cfg.DisableDNSSeed = true
	}

	// Add the default listener if none were specified. The default
	// listener is all addresses on the listen port for the network
	// we are to connect to.
	if len(cfg.Listeners) == 0 {
		cfg.Listeners = []string{
			net.JoinHostPort("", activeNetParams.listenPort),
		}
	}

	// The RPC server is disabled if no username or password is provided.
	if cfg.RPCUser == "" || cfg.RPCPass == "" {
		cfg.DisableRPC = true
	}

	// Add default port to all listner addresses if needed and remove
	// duplicate addresses.
	cfg.Listeners = cleanListenAddresses(cfg.Listeners)

	// Add default port to all added peer addresses if needed and remove
	// duplicate addresses.
	cfg.AddPeers = cleanPeerAddresses(cfg.AddPeers)
	cfg.ConnectPeers = cleanPeerAddresses(cfg.ConnectPeers)

	return &cfg, remainingArgs, nil
}
<commit_msg>Don't load default config file in regtest mode.<commit_after>// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"github.com/conformal/btcdb"
	_ "github.com/conformal/btcdb/ldb"
	"github.com/conformal/btcutil"
	"github.com/conformal/btcwire"
	"github.com/conformal/go-flags"
	"net"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

const (
	defaultConfigFilename = "btcd.conf"
	defaultDataDirname    = "data"
	defaultLogLevel       = "info"
	defaultBtcnet         = btcwire.MainNet
	defaultMaxPeers       = 125
	defaultBanDuration    = time.Hour * 24
	defaultVerifyEnabled  = false
	defaultDbType         = "leveldb"
)

var (
	btcdHomeDir       = btcutil.AppDataDir("btcd", false)
	defaultConfigFile = filepath.Join(btcdHomeDir, defaultConfigFilename)
	defaultDataDir    = filepath.Join(btcdHomeDir, defaultDataDirname)
	defaultListener   = net.JoinHostPort("", netParams(defaultBtcnet).listenPort)
	knownDbTypes      = btcdb.SupportedDBs()
)

// config defines the configuration options for btcd.
//
// See loadConfig for details on the configuration load process.
type config struct {
	ShowVersion        bool          `short:"V" long:"version" description:"Display version information and exit"`
	ConfigFile         string        `short:"C" long:"configfile" description:"Path to configuration file"`
	DataDir            string        `short:"b" long:"datadir" description:"Directory to store data"`
	AddPeers           []string      `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
	ConnectPeers       []string      `long:"connect" description:"Connect only to the specified peers at startup"`
	DisableListen      bool          `long:"nolisten" description:"Disable listening for incoming connections -- NOTE: Listening is automatically disabled if the --connect option is used or if the --proxy option is used without the --tor option"`
	Listeners          []string      `long:"listen" description:"Listen for connections on this interface/port (default all interfaces port: 8333, testnet: 18333)"`
	MaxPeers           int           `long:"maxpeers" description:"Max number of inbound and outbound peers"`
	BanDuration        time.Duration `long:"banduration" description:"How long to ban misbehaving peers.  Valid time units are {s, m, h}.  Minimum 1 second"`
	RPCUser            string        `short:"u" long:"rpcuser" description:"Username for RPC connections"`
	RPCPass            string        `short:"P" long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
	RPCPort            string        `short:"r" long:"rpcport" description:"Listen for JSON/RPC messages on this port"`
	DisableRPC         bool          `long:"norpc" description:"Disable built-in RPC server -- NOTE: The RPC server is disabled by default if no rpcuser/rpcpass is specified"`
	DisableDNSSeed     bool          `long:"nodnsseed" description:"Disable DNS seeding for peers"`
	Proxy              string        `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
	ProxyUser          string        `long:"proxyuser" description:"Username for proxy server"`
	ProxyPass          string        `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
	UseTor             bool          `long:"tor" description:"Specifies the proxy server used is a Tor node"`
	TestNet3           bool          `long:"testnet" description:"Use the test network"`
	RegressionTest     bool          `long:"regtest" description:"Use the regression test network"`
	DisableCheckpoints bool          `long:"nocheckpoints" description:"Disable built-in checkpoints.  Don't do this unless you know what you're doing."`
	DbType             string        `long:"dbtype" description:"Database backend to use for the Block Chain"`
	Profile            string        `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
	CpuProfile         string        `long:"cpuprofile" description:"Write CPU profile to the specified file"`
	DebugLevel         string        `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
}

// cleanAndExpandPath expands environement variables and leading ~ in the
// passed path, cleans the result, and returns it.
func cleanAndExpandPath(path string) string {
	// Expand initial ~ to OS specific home directory.
	if strings.HasPrefix(path, "~") {
		homeDir := filepath.Dir(btcdHomeDir)
		path = strings.Replace(path, "~", homeDir, 1)
	}

	// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
	// but they variables can still be expanded via POSIX-style $VARIABLE.
	return filepath.Clean(os.ExpandEnv(path))
}

// validLogLevel returns whether or not logLevel is a valid debug log level.
func validLogLevel(logLevel string) bool {
	switch logLevel {
	case "trace":
		fallthrough
	case "debug":
		fallthrough
	case "info":
		fallthrough
	case "warn":
		fallthrough
	case "error":
		fallthrough
	case "critical":
		return true
	}
	return false
}

// validDbType returns whether or not dbType is a supported database type.
func validDbType(dbType string) bool {
	for _, knownType := range knownDbTypes {
		if dbType == knownType {
			return true
		}
	}

	return false
}

// removeDuplicateAddresses returns a new slice with all duplicate entries in
// addrs removed.
func removeDuplicateAddresses(addrs []string) []string {
	result := make([]string, 0)
	seen := map[string]bool{}
	for _, val := range addrs {
		if _, ok := seen[val]; !ok {
			result = append(result, val)
			seen[val] = true
		}
	}
	return result
}

// normalizeAddress returns addr with the passed default port appended if
// there is not already a port specified.
func normalizeAddress(addr, defaultPort string) string {
	_, _, err := net.SplitHostPort(addr)
	if err != nil {
		return net.JoinHostPort(addr, defaultPort)
	}
	return addr
}

// normalizeAddresses returns a new slice with all the passed peer addresses
// normalized with the given default port, and all duplicates removed.
func normalizeAddresses(addrs []string, defaultPort string) []string {
	for i, addr := range addrs {
		addrs[i] = normalizeAddress(addr, defaultPort)
	}

	return removeDuplicateAddresses(addrs)
}

// cleanListenAddresses returns a new slice with all the passed peer addresses
// normalized and duplicates removed.
func cleanListenAddresses(addrs []string) []string {
	return normalizeAddresses(addrs, activeNetParams.listenPort)
}

// cleanPeerAddresses returns a new slice with all the passed peer addresses
// normalized and duplicates removed.
func cleanPeerAddresses(addrs []string) []string {
	return normalizeAddresses(addrs, activeNetParams.peerPort)
}

// updateConfigWithActiveParams update the passed config with parameters
// from the active net params if the relevant options in the passed config
// object are the default so options specified by the user on the command line
// are not overridden.
func updateConfigWithActiveParams(cfg *config) {
	// Even though there should only be one default, a duplicate might
	// have been specified via the config file or CLI options.  So, make
	// sure to update all default entries rather than only the first one
	// found.  Duplicates are removed later.

	if cfg.RPCPort == netParams(defaultBtcnet).rpcPort {
		cfg.RPCPort = activeNetParams.rpcPort
	}
}

// filesExists reports whether the named file or directory exists.
func fileExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// loadConfig initializes and parses the config using a config file and command
// line options.
//
// The configuration proceeds as follows:
// 	1) Start with a default config with sane settings
// 	2) Pre-parse the command line to check for an alternative config file
// 	3) Load configuration file overwriting defaults with any specified options
// 	4) Parse CLI options and overwrite/add any specified options
//
// The above results in btcd functioning properly without any config settings
// while still allowing the user to override settings with config files and
// command line options.  Command line options always take precedence.
func loadConfig() (*config, []string, error) {
	// Default config.
	cfg := config{
		DebugLevel:  defaultLogLevel,
		RPCPort:     netParams(defaultBtcnet).rpcPort,
		MaxPeers:    defaultMaxPeers,
		BanDuration: defaultBanDuration,
		ConfigFile:  defaultConfigFile,
		DataDir:     defaultDataDir,
		DbType:      defaultDbType,
	}

	// Pre-parse the command line options to see if an alternative config
	// file or the version flag was specified.  Any errors can be ignored
	// here since they will be caught be the final parse below.
	preCfg := cfg
	preParser := flags.NewParser(&preCfg, flags.None)
	preParser.Parse()

	// Show the version and exit if the version flag was specified.
	if preCfg.ShowVersion {
		appName := filepath.Base(os.Args[0])
		appName = strings.TrimSuffix(appName, filepath.Ext(appName))
		fmt.Println(appName, "version", version())
		os.Exit(0)
	}

	// Load additional config from file.
	parser := flags.NewParser(&cfg, flags.Default)
	if !preCfg.RegressionTest || preCfg.ConfigFile != defaultConfigFile {
		err := parser.ParseIniFile(preCfg.ConfigFile)
		if err != nil {
			if _, ok := err.(*os.PathError); !ok {
				fmt.Fprintln(os.Stderr, err)
				parser.WriteHelp(os.Stderr)
				return nil, nil, err
			}
			log.Warnf("%v", err)
		}
	}

	// Don't add peers from the config file when in regression test mode.
	if preCfg.RegressionTest && len(cfg.AddPeers) > 0 {
		cfg.AddPeers = nil
	}

	// Parse command line options again to ensure they take precedence.
	remainingArgs, err := parser.Parse()
	if err != nil {
		if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
			parser.WriteHelp(os.Stderr)
		}
		return nil, nil, err
	}

	// The two test networks can't be selected simultaneously.
	if cfg.TestNet3 && cfg.RegressionTest {
		str := "%s: The testnet and regtest params can't be used " +
			"together -- choose one of the two"
		err := fmt.Errorf(str, "loadConfig")
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Choose the active network params based on the testnet and regression
	// test net flags.
	if cfg.TestNet3 {
		activeNetParams = netParams(btcwire.TestNet3)
	} else if cfg.RegressionTest {
		activeNetParams = netParams(btcwire.TestNet)
	}
	updateConfigWithActiveParams(&cfg)

	// Validate debug log level.
	if !validLogLevel(cfg.DebugLevel) {
		str := "%s: The specified debug level [%v] is invalid"
		err := fmt.Errorf(str, "loadConfig", cfg.DebugLevel)
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Validate database type.
	if !validDbType(cfg.DbType) {
		str := "%s: The specified database type [%v] is invalid -- " +
			"supported types %v"
		err := fmt.Errorf(str, "loadConfig", cfg.DbType, knownDbTypes)
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Validate profile port number
	if cfg.Profile != "" {
		profilePort, err := strconv.Atoi(cfg.Profile)
		if err != nil || profilePort < 1024 || profilePort > 65535 {
			str := "%s: The profile port must be between 1024 and 65535"
			err := fmt.Errorf(str, "loadConfig")
			fmt.Fprintln(os.Stderr, err)
			parser.WriteHelp(os.Stderr)
			return nil, nil, err
		}
	}

	// Append the network type to the data directory so it is "namespaced"
	// per network.  In addition to the block database, there are other
	// pieces of data that are saved to disk such as address manager state.
	// All data is specific to a network, so namespacing the data directory
	// means each individual piece of serialized data does not have to
	// worry about changing names per network and such.
	cfg.DataDir = cleanAndExpandPath(cfg.DataDir)
	cfg.DataDir = filepath.Join(cfg.DataDir, activeNetParams.netName)

	// Don't allow ban durations that are too short.
	if cfg.BanDuration < time.Duration(time.Second) {
		str := "%s: The banduration option may not be less than 1s -- parsed [%v]"
		err := fmt.Errorf(str, "loadConfig", cfg.BanDuration)
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --addPeer and --connect do not mix.
	if len(cfg.AddPeers) > 0 && len(cfg.ConnectPeers) > 0 {
		str := "%s: the --addpeer and --connect options can not be " +
			"mixed"
		err := fmt.Errorf(str, "loadConfig")
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --tor requires --proxy to be set.
	if cfg.UseTor && cfg.Proxy == "" {
		str := "%s: the --tor option requires --proxy to be set"
		err := fmt.Errorf(str, "loadConfig")
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --proxy or --connect without --listen disables listening.
	if (cfg.Proxy != "" || len(cfg.ConnectPeers) > 0) &&
		len(cfg.Listeners) == 0 {
		cfg.DisableListen = true
	}

	// Connect means no DNS seeding.
	if len(cfg.ConnectPeers) > 0 {
		cfg.DisableDNSSeed = true
	}

	// Add the default listener if none were specified. The default
	// listener is all addresses on the listen port for the network
	// we are to connect to.
	if len(cfg.Listeners) == 0 {
		cfg.Listeners = []string{
			net.JoinHostPort("", activeNetParams.listenPort),
		}
	}

	// The RPC server is disabled if no username or password is provided.
	if cfg.RPCUser == "" || cfg.RPCPass == "" {
		cfg.DisableRPC = true
	}

	// Add default port to all listner addresses if needed and remove
	// duplicate addresses.
	cfg.Listeners = cleanListenAddresses(cfg.Listeners)

	// Add default port to all added peer addresses if needed and remove
	// duplicate addresses.
	cfg.AddPeers = cleanPeerAddresses(cfg.AddPeers)
	cfg.ConnectPeers = cleanPeerAddresses(cfg.ConnectPeers)

	return &cfg, remainingArgs, nil
}
<|endoftext|>


<commit_before>package config

import (
	"fmt"
	"git.name.im/bamboo/log.git"
	"github.com/howeyc/fsnotify"
	"github.com/vaughan0/go-ini"
	"path/filepath"
	"strings"
)

type conf map[string]ini.File

// 对外提供的config参数
var Config  = conf{}

// 是否为测试环境
var debug bool

// SetDebug 设置测试环境
func SetDebug(b bool) {
	debug = b
}

// Init 初始化配置文件
func (c *conf) Init() {
	// 加载所有的配置文件
	var pattern string
	if debug {
		pattern = fmt.Sprintf("%s%s", c.ConfigPath(), "*_debug.ini")
	} else {
		pattern = fmt.Sprintf("%s%s", c.ConfigPath(), "*.ini")
	}

	// 扫描目录找到配置文件
	fileList, err := filepath.Glob(pattern)
	if err != nil {
		log.Panic(err)
	}

	// 开始加载配置文件
	for _, v := range fileList {
		c.loadFile(v)
	}

	// 文件加载完成的时候，开始监听文件的变化
	go c.Watch()

}

// Watcher 监听文件的变化
func (c *conf) Watch() {
	configPath := c.ConfigPath()

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Panic(err)
	}
	defer watcher.Close()

	err = watcher.Watch(configPath)
	if err != nil {
		log.Panic(err)
	}

	fileSuffix := c.fileSuffix()

	for {
		select {
		case v := <-watcher.Event:
			// 判断是否为配置文件变化
			if !strings.HasSuffix(v.Name, fileSuffix) {
				break
			}

			// 当为删除、重命名操作时，删除对应的配置项
			if v.IsDelete() || v.IsRename() {
				delete((*c), c.fileKey(v.Name))
			}

			// 当为修改、创建操作时
			if v.IsModify() || v.IsCreate() {
				c.loadFile(v.Name)
			}
		}
	}

}

// loadFile 加载配置文件
func (c *conf) loadFile(filePath string) bool {
	tmp, err := ini.LoadFile(filePath)
	if err != nil {
		log.Warring(err)
		return false
	}
	// 计算文件的名称
	(*c)[c.fileKey(filePath)] = tmp
	return true
}

// fileKey　通过文件地址获取文件对应的key
func (c *conf) fileKey(filePath string) string {
	return strings.Replace(filepath.Base(filePath), c.fileSuffix(), "", -1)
}

// fileSuffix 配置文件后缀
func (c *conf) fileSuffix() string {
	var fileSuffix string
	if debug {
		fileSuffix = "_debug.ini"
	} else {
		fileSuffix = ".ini"
	}
	return fileSuffix
}

// ConfigPath 获取配置文件存储目录
func (c *conf) ConfigPath() string {
	return fmt.Sprintf("%s/config/", log.RunDir())
}
<commit_msg>当为debug环境时，当没有对应的debug配置文件时，直接使用线上的配置文件<commit_after>package config

import (
	"fmt"
	"git.name.im/bamboo/log.git"
	"github.com/howeyc/fsnotify"
	"github.com/vaughan0/go-ini"
	"os"
	"path/filepath"
	"strings"
)

type conf map[string]ini.File

// 对外提供的config参数
var Config = conf{}

// 是否为测试环境
var debug bool

// SetDebug 设置测试环境
func SetDebug(b bool) {
	debug = b
	log.SetDebug(b)
}

const (
	NORAML_SUFFIX = ".ini"       // 正常配置文件后缀
	DEBUG_SUFFIX  = "_debug.ini" // debug模式配置文件后缀
)

// Init 初始化配置文件
func (c *conf) Init() {
	// 加载所有的配置文件
	pattern := fmt.Sprintf("%s*%s", c.ConfigPath(), NORAML_SUFFIX)

	// 扫描目录找到配置文件
	fileList, err := filepath.Glob(pattern)
	if err != nil {
		log.Panic(err)
	}

	// 开始加载配置文件
	for _, v := range fileList {
		if !debug {
			if strings.Index(v, DEBUG_SUFFIX) > -1 {
				continue
			}
		}
		c.loadFile(v)
	}

	// 文件加载完成的时候，开始监听文件的变化
	go c.Watch()

}

// Watcher 监听文件的变化
func (c *conf) Watch() {
	configPath := c.ConfigPath()

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Panic(err)
	}
	defer watcher.Close()

	err = watcher.Watch(configPath)
	if err != nil {
		log.Panic(err)
	}

	fileSuffix := c.fileSuffix()

	for {
		select {
		case v := <-watcher.Event:
			// 判断是否为配置文件变化
			if !strings.HasSuffix(v.Name, fileSuffix) {
				break
			}

			// 当为删除、重命名操作时，删除对应的配置项
			if v.IsDelete() || v.IsRename() {
				delete((*c), c.fileKey(v.Name))
			}

			// 当为修改、创建操作时
			if v.IsModify() || v.IsCreate() {
				c.loadFile(v.Name)
			}
		}
	}

}

// loadFile 加载配置文件
func (c *conf) loadFile(filePath string) bool {
	var tmp ini.File
	var err error
	for {
		tmp, err = ini.LoadFile(filePath)
		if err != nil {
			if err == os.ErrNotExist && debug {
				filePath = strings.Replace(filePath, DEBUG_SUFFIX, NORAML_SUFFIX, 1)
			} else {
				log.Warring(err)
				return false
			}
		} else {
			break
		}
	}

	// 计算文件的名称
	(*c)[c.fileKey(filePath)] = tmp
	return true
}

// fileKey　通过文件地址获取文件对应的key
// 通过 filePath 参数来获取对应的文件名做为key
func (c *conf) fileKey(filePath string) string {
	return strings.Replace(filepath.Base(filePath), c.fileSuffix(filePath), "", -1)
}

// fileSuffix 配置文件对应的后缀
// filePath 当设置了文件路径的时候，通过判断是否有debug文件后缀来确定其使用的是哪种后缀
func (c *conf) fileSuffix(filePath ...string) string {
	var fileSuffix string
	if len(filePath) > 0 {
		if strings.Index(filePath[0], DEBUG_SUFFIX) > 1 {
			fileSuffix = DEBUG_SUFFIX
		} else {
			fileSuffix = NORAML_SUFFIX
		}
	} else {
		if debug {
			fileSuffix = DEBUG_SUFFIX
		} else {
			fileSuffix = NORAML_SUFFIX
		}
	}

	return fileSuffix
}

// ConfigPath 获取配置文件存储目录
func (c *conf) ConfigPath() string {
	return fmt.Sprintf("%s/config/", log.RunDir())
}
<|endoftext|>


<commit_before>/*
 * Configuration management stuff. This configuration is built from YAML
 * configuration file:
 *
 *   directory:   /tmp
 *   repo_hash:   /tmp/repo-status.yml
 *   port:        6666
 *   email:
 *     smtp_host: smtp.example.com:25
 *     recipient: nobody@nowhere.com
 *     sender:    nobody@nowhere.com
 *     success:   true
 *     once:      true
 *   modules:
 *   - name:    continuum
 *     url:     git@github.com:c4s4/continuum.git
 *     branch:  develop
 *     command: |
 *       set -e
 *       make test
 */

package main

import (
	"gopkg.in/yaml.v1"
	"io/ioutil"
	"path/filepath"
)

// EmailConfig is the configuration to send email report.
type EmailConfig struct {
	SmtpHost  string "smtp-host"
	Recipient string
	Sender    string
	Success   bool
	Once      bool
}

// ModuleConfig is the configuration for a given module.
type ModuleConfig struct {
	Name    string
	Url     string
	Branch  string
	Command string
}

// Config is the global configuration of the application.
type Config struct {
	Directory string
	Status    string
	Port      int
	Email     EmailConfig
	Modules   []ModuleConfig
}

// LoadConfig loads configuration from a given YAML configuration file.
func LoadConfig(file string) Config {
	config := Config{}
	text, err := ioutil.ReadFile(file)
	if err != nil {
		panic(err.Error())
	}
	err = yaml.Unmarshal(text, &config)
	if err != nil {
		panic(err.Error())
	}
	// make directory absolute path
	absdir, err := filepath.Abs(config.Directory)
	if err != nil {
		panic(err.Error())
	}
	config.Directory = absdir
	return config
}
<commit_msg>Fixed go_vet warning<commit_after>/*
 * Configuration management stuff. This configuration is built from YAML
 * configuration file:
 *
 *   directory:   /tmp
 *   repo_hash:   /tmp/repo-status.yml
 *   port:        6666
 *   email:
 *     smtp_host: smtp.example.com:25
 *     recipient: nobody@nowhere.com
 *     sender:    nobody@nowhere.com
 *     success:   true
 *     once:      true
 *   modules:
 *   - name:    continuum
 *     url:     git@github.com:c4s4/continuum.git
 *     branch:  develop
 *     command: |
 *       set -e
 *       make test
 */

package main

import (
	"gopkg.in/yaml.v1"
	"io/ioutil"
	"path/filepath"
)

// EmailConfig is the configuration to send email report.
type EmailConfig struct {
	SmtpHost  string `yaml:"smtp-host"`
	Recipient string
	Sender    string
	Success   bool
	Once      bool
}

// ModuleConfig is the configuration for a given module.
type ModuleConfig struct {
	Name    string
	Url     string
	Branch  string
	Command string
}

// Config is the global configuration of the application.
type Config struct {
	Directory string
	Status    string
	Port      int
	Email     EmailConfig
	Modules   []ModuleConfig
}

// LoadConfig loads configuration from a given YAML configuration file.
func LoadConfig(file string) Config {
	config := Config{}
	text, err := ioutil.ReadFile(file)
	if err != nil {
		panic(err.Error())
	}
	err = yaml.Unmarshal(text, &config)
	if err != nil {
		panic(err.Error())
	}
	// make directory absolute path
	absdir, err := filepath.Abs(config.Directory)
	if err != nil {
		panic(err.Error())
	}
	config.Directory = absdir
	return config
}
<|endoftext|>


<commit_before>package isolated

import (
	"code.cloudfoundry.org/cli/integration/helpers"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("get-health-check command", func() {
	Describe("help", func() {
		Context("when --help flag is set", func() {
			It("Displays command usage to output", func() {
				session := helpers.CF("get-health-check", "--help")

				Eventually(session.Out).Should(Say("NAME:"))
				Eventually(session.Out).Should(Say("   get-health-check - Show the type of health check performed on an app"))
				Eventually(session.Out).Should(Say("USAGE:"))
				Eventually(session.Out).Should(Say("   cf get-health-check APP_NAME"))
				Eventually(session).Should(Exit(0))
			})
		})
	})

	Context("when the environment is not setup correctly", func() {
		Context("when no API endpoint is set", func() {
			BeforeEach(func() {
				helpers.UnsetAPI()
			})

			It("fails with no API endpoint set message", func() {
				session := helpers.CF("get-health-check", "some-app")

				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("No API endpoint set\\. Use 'cf login' or 'cf api' to target an endpoint\\."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when not logged in", func() {
			BeforeEach(func() {
				helpers.LogoutCF()
			})

			It("fails with not logged in message", func() {
				session := helpers.CF("get-health-check", "some-app")

				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("Not logged in\\. Use 'cf login' to log in\\."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when there is no org and space set", func() {
			BeforeEach(func() {
				helpers.LogoutCF()
				helpers.LoginCF()
			})

			It("fails with no targeted org error message", func() {
				session := helpers.CF("get-health-check", "some-app")

				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("No org targeted, use 'cf target -o ORG' to target an org"))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when there is no space set", func() {
			BeforeEach(func() {
				helpers.LogoutCF()
				helpers.LoginCF()
				helpers.TargetOrg(ReadOnlyOrg)
			})

			It("fails with no targeted space error message", func() {
				session := helpers.CF("get-health-check", "some-app")

				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("No space targeted, use 'cf target -s SPACE' to target a space\\."))
				Eventually(session).Should(Exit(1))
			})
		})
	})

	Context("when the environment is set up correctly", func() {
		var (
			orgName   string
			spaceName string
		)

		BeforeEach(func() {
			orgName = helpers.NewOrgName()
			spaceName = helpers.PrefixedRandomName("SPACE")

			setupCF(orgName, spaceName)
		})

		Context("when the input is invalid", func() {
			Context("when there are not enough arguments", func() {
				It("outputs the usage and exits 1", func() {
					session := helpers.CF("get-health-check")

					Eventually(session.Err).Should(Say("Incorrect Usage:"))
					Eventually(session.Out).Should(Say("NAME:"))
					Eventually(session).Should(Exit(1))
				})
			})

			Context("when there too many arguments", func() {
				It("ignores the extra arguments", func() {
					appName := helpers.PrefixedRandomName("app")
					session := helpers.CF("get-health-check", appName, "extra")
					username, _ := helpers.GetCredentials()

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Err).Should(Say("App %s not found", appName))
					Eventually(session.Out).Should(Say("FAILED"))
					Eventually(session).Should(Exit(1))
				})
			})
		})

		Context("when the app does not exist", func() {
			It("tells the user that the app is not found and exits 1", func() {
				appName := helpers.PrefixedRandomName("app")
				session := helpers.CF("get-health-check", appName)
				username, _ := helpers.GetCredentials()

				Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
				Eventually(session.Err).Should(Say("App %s not found", appName))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when the app exists", func() {
			var (
				appName  string
				username string
			)

			BeforeEach(func() {
				appName = helpers.PrefixedRandomName("app")
				helpers.WithHelloWorldApp(func(appDir string) {
					Eventually(helpers.CF("push", appName, "-p", appDir, "-b", "staticfile_buildpack", "--no-start")).Should(Exit(0))
				})
				username, _ = helpers.GetCredentials()
			})

			Context("when the health check type is http", func() {
				BeforeEach(func() {
					Eventually(helpers.CF("set-health-check", appName, "http")).Should(Exit(0))
				})

				It("shows an endpoint", func() {
					session := helpers.CF("get-health-check", appName)

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Out).Should(Say("\n\n"))
					Eventually(session.Out).Should(Say("Health check type:          http"))
					Eventually(session.Out).Should(Say("Endpoint \\(for http type\\):   /"))
					Eventually(session).Should(Exit(0))
				})
			})

			Context("when the health check type is http with a custom endpoint", func() {
				BeforeEach(func() {
					Eventually(helpers.CF("set-health-check", appName, "http", "--endpoint", "/some-endpoint")).Should(Exit(0))
				})

				It("show the custom endpoint", func() {
					session := helpers.CF("get-health-check", appName)

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Out).Should(Say("\n\n"))
					Eventually(session.Out).Should(Say("Health check type:          http"))
					Eventually(session.Out).Should(Say("Endpoint \\(for http type\\):   /some-endpoint"))
					Eventually(session).Should(Exit(0))
				})
			})

			Context("when the health check type is none", func() {
				BeforeEach(func() {
					Eventually(helpers.CF("set-health-check", appName, "none")).Should(Exit(0))
				})

				It("does not show an endpoint", func() {
					session := helpers.CF("get-health-check", appName)

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Out).Should(Say("\n\n"))
					Eventually(session.Out).Should(Say("Health check type:          none"))
					Eventually(session.Out).Should(Say("Endpoint \\(for http type\\):   \n"))
					Eventually(session).Should(Exit(0))
				})
			})

			Context("when the health check type is port", func() {
				BeforeEach(func() {
					Eventually(helpers.CF("set-health-check", appName, "port")).Should(Exit(0))
				})

				It("does not show an endpoint", func() {
					session := helpers.CF("get-health-check", appName)

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Out).Should(Say("\n\n"))
					Eventually(session.Out).Should(Say("Health check type:          port"))
					Eventually(session.Out).Should(Say("Endpoint \\(for http type\\):   \n"))
					Eventually(session).Should(Exit(0))
				})
			})

			Context("when the health check type is process", func() {
				BeforeEach(func() {
					Eventually(helpers.CF("set-health-check", appName, "process")).Should(Exit(0))
				})

				It("does not show an endpoint", func() {
					session := helpers.CF("get-health-check", appName)

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Out).Should(Say("\n\n"))
					Eventually(session.Out).Should(Say("Health check type:          process"))
					Eventually(session.Out).Should(Say("Endpoint \\(for http type\\):   \n"))
					Eventually(session).Should(Exit(0))
				})
			})

			Context("when the health check type changes from http to another type", func() {
				BeforeEach(func() {
					Eventually(helpers.CF("set-health-check", appName, "http", "--endpoint", "/some-endpoint")).Should(Exit(0))
					Eventually(helpers.CF("set-health-check", appName, "process")).Should(Exit(0))
				})

				It("does not show an endpoint", func() {
					session := helpers.CF("get-health-check", appName)

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Out).Should(Say("\n\n"))
					Eventually(session.Out).Should(Say("Health check type:          process"))
					Eventually(session.Out).Should(Say("Endpoint \\(for http type\\):   \n"))
					Eventually(session).Should(Exit(0))
				})
			})
		})
	})
})
<commit_msg>use $ instead of \n for matching end of line<commit_after>package isolated

import (
	"code.cloudfoundry.org/cli/integration/helpers"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("get-health-check command", func() {
	Describe("help", func() {
		Context("when --help flag is set", func() {
			It("Displays command usage to output", func() {
				session := helpers.CF("get-health-check", "--help")

				Eventually(session.Out).Should(Say("NAME:"))
				Eventually(session.Out).Should(Say("   get-health-check - Show the type of health check performed on an app"))
				Eventually(session.Out).Should(Say("USAGE:"))
				Eventually(session.Out).Should(Say("   cf get-health-check APP_NAME"))
				Eventually(session).Should(Exit(0))
			})
		})
	})

	Context("when the environment is not setup correctly", func() {
		Context("when no API endpoint is set", func() {
			BeforeEach(func() {
				helpers.UnsetAPI()
			})

			It("fails with no API endpoint set message", func() {
				session := helpers.CF("get-health-check", "some-app")

				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("No API endpoint set\\. Use 'cf login' or 'cf api' to target an endpoint\\."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when not logged in", func() {
			BeforeEach(func() {
				helpers.LogoutCF()
			})

			It("fails with not logged in message", func() {
				session := helpers.CF("get-health-check", "some-app")

				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("Not logged in\\. Use 'cf login' to log in\\."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when there is no org and space set", func() {
			BeforeEach(func() {
				helpers.LogoutCF()
				helpers.LoginCF()
			})

			It("fails with no targeted org error message", func() {
				session := helpers.CF("get-health-check", "some-app")

				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("No org targeted, use 'cf target -o ORG' to target an org"))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when there is no space set", func() {
			BeforeEach(func() {
				helpers.LogoutCF()
				helpers.LoginCF()
				helpers.TargetOrg(ReadOnlyOrg)
			})

			It("fails with no targeted space error message", func() {
				session := helpers.CF("get-health-check", "some-app")

				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("No space targeted, use 'cf target -s SPACE' to target a space\\."))
				Eventually(session).Should(Exit(1))
			})
		})
	})

	Context("when the environment is set up correctly", func() {
		var (
			orgName   string
			spaceName string
		)

		BeforeEach(func() {
			orgName = helpers.NewOrgName()
			spaceName = helpers.PrefixedRandomName("SPACE")

			setupCF(orgName, spaceName)
		})

		Context("when the input is invalid", func() {
			Context("when there are not enough arguments", func() {
				It("outputs the usage and exits 1", func() {
					session := helpers.CF("get-health-check")

					Eventually(session.Err).Should(Say("Incorrect Usage:"))
					Eventually(session.Out).Should(Say("NAME:"))
					Eventually(session).Should(Exit(1))
				})
			})

			Context("when there too many arguments", func() {
				It("ignores the extra arguments", func() {
					appName := helpers.PrefixedRandomName("app")
					session := helpers.CF("get-health-check", appName, "extra")
					username, _ := helpers.GetCredentials()

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Err).Should(Say("App %s not found", appName))
					Eventually(session.Out).Should(Say("FAILED"))
					Eventually(session).Should(Exit(1))
				})
			})
		})

		Context("when the app does not exist", func() {
			It("tells the user that the app is not found and exits 1", func() {
				appName := helpers.PrefixedRandomName("app")
				session := helpers.CF("get-health-check", appName)
				username, _ := helpers.GetCredentials()

				Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
				Eventually(session.Err).Should(Say("App %s not found", appName))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when the app exists", func() {
			var (
				appName  string
				username string
			)

			BeforeEach(func() {
				appName = helpers.PrefixedRandomName("app")
				helpers.WithHelloWorldApp(func(appDir string) {
					Eventually(helpers.CF("push", appName, "-p", appDir, "-b", "staticfile_buildpack", "--no-start")).Should(Exit(0))
				})
				username, _ = helpers.GetCredentials()
			})

			Context("when the health check type is http", func() {
				BeforeEach(func() {
					Eventually(helpers.CF("set-health-check", appName, "http")).Should(Exit(0))
				})

				It("shows an endpoint", func() {
					session := helpers.CF("get-health-check", appName)

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Out).Should(Say("\n\n"))
					Eventually(session.Out).Should(Say("Health check type:          http"))
					Eventually(session.Out).Should(Say("Endpoint \\(for http type\\):   /"))
					Eventually(session).Should(Exit(0))
				})
			})

			Context("when the health check type is http with a custom endpoint", func() {
				BeforeEach(func() {
					Eventually(helpers.CF("set-health-check", appName, "http", "--endpoint", "/some-endpoint")).Should(Exit(0))
				})

				It("show the custom endpoint", func() {
					session := helpers.CF("get-health-check", appName)

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Out).Should(Say("\n\n"))
					Eventually(session.Out).Should(Say("Health check type:          http"))
					Eventually(session.Out).Should(Say("Endpoint \\(for http type\\):   /some-endpoint"))
					Eventually(session).Should(Exit(0))
				})
			})

			Context("when the health check type is none", func() {
				BeforeEach(func() {
					Eventually(helpers.CF("set-health-check", appName, "none")).Should(Exit(0))
				})

				It("does not show an endpoint", func() {
					session := helpers.CF("get-health-check", appName)

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Out).Should(Say("\n\n"))
					Eventually(session.Out).Should(Say("Health check type:          none"))
					Eventually(session.Out).Should(Say("(?m)Endpoint \\(for http type\\):   $"))
					Eventually(session).Should(Exit(0))
				})
			})

			Context("when the health check type is port", func() {
				BeforeEach(func() {
					Eventually(helpers.CF("set-health-check", appName, "port")).Should(Exit(0))
				})

				It("does not show an endpoint", func() {
					session := helpers.CF("get-health-check", appName)

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Out).Should(Say("\n\n"))
					Eventually(session.Out).Should(Say("Health check type:          port"))
					Eventually(session.Out).Should(Say("(?m)Endpoint \\(for http type\\):   $"))
					Eventually(session).Should(Exit(0))
				})
			})

			Context("when the health check type is process", func() {
				BeforeEach(func() {
					Eventually(helpers.CF("set-health-check", appName, "process")).Should(Exit(0))
				})

				It("does not show an endpoint", func() {
					session := helpers.CF("get-health-check", appName)

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Out).Should(Say("\n\n"))
					Eventually(session.Out).Should(Say("Health check type:          process"))
					Eventually(session.Out).Should(Say("(?m)Endpoint \\(for http type\\):   $"))
					Eventually(session).Should(Exit(0))
				})
			})

			Context("when the health check type changes from http to another type", func() {
				BeforeEach(func() {
					Eventually(helpers.CF("set-health-check", appName, "http", "--endpoint", "/some-endpoint")).Should(Exit(0))
					Eventually(helpers.CF("set-health-check", appName, "process")).Should(Exit(0))
				})

				It("does not show an endpoint", func() {
					session := helpers.CF("get-health-check", appName)

					Eventually(session.Out).Should(Say("Getting health check type for app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, username))
					Eventually(session.Out).Should(Say("\n\n"))
					Eventually(session.Out).Should(Say("Health check type:          process"))
					Eventually(session.Out).Should(Say("(?m)Endpoint \\(for http type\\):   $"))
					Eventually(session).Should(Exit(0))
				})
			})
		})
	})
})
<|endoftext|>


<commit_before>package main

var AvailableWidgets = map[string]Widget{
    "queries": Widget{
        graph_title: "ClickHouse queries",
        graph_category: "clickhouse",
        graph_info: "Values received from ClickHouse system.events table",
        graph_vlabel: "queries / second",
        graph_period: "second",
        graph_args: "--lower-limit 0",
        metrics: []MetricConfig{
            MetricConfig{
                id: "select",
                label: "Selects",
                metric_type: "DERIVE",
                draw_type: "AREA",
                color: "COLOUR0",
                clickhouseEvent: "SelectQuery",
            },
            MetricConfig{
                id: "insert",
                label: "Inserts",
                metric_type: "DERIVE",
                draw_type: "STACK",
                color: "COLOUR1",
                clickhouseEvent: "InsertQuery",
            },
        },
    },
    "files": Widget{
        graph_title: "ClickHouse files",
        graph_category: "clickhouse",
        graph_info: "Values received from ClickHouse system.events table",
        graph_vlabel: "operations / second",
        graph_period: "second",
        graph_args: "--lower-limit 0",
        metrics: []MetricConfig{
            MetricConfig{
                id: "file_open",
                label: "Opens",
                metric_type: "DERIVE",
                draw_type: "AREA",
                color: "COLOUR0",
                clickhouseEvent: "FileOpen",
            },
            MetricConfig{
                id: "seek",
                label: "Seeks",
                metric_type: "DERIVE",
                draw_type: "STACK",
                color: "COLOUR1",
                clickhouseEvent: "Seek",
            },
        },
    },
    "cache": Widget{
        graph_title: "ClickHouse cache",
        graph_category: "clickhouse",
        graph_info: "Values received from ClickHouse system.events table",
        graph_vlabel: "per second",
        graph_period: "second",
        graph_args: "-u 100 -l 0 -r --base 1000",
        metrics: []MetricConfig{
            MetricConfig{
                id: "hits",
                label: "hit ratio",
                metric_type: "GAUGE",
                draw_type: "AREA",
                color: "COLOUR0",
                clickhouseEvent: "MarkCacheHits",
            },
            MetricConfig{
                id: "misses",
                label: "miss ratio",
                metric_type: "GAUGE",
                draw_type: "STACK",
                color: "COLOUR1",
                clickhouseEvent: "MarkCacheMisses",
            },
        },
    },
}
<commit_msg>add zookeeper widget<commit_after>package main

var AvailableWidgets = map[string]Widget{
    "queries": Widget{
        graph_title: "ClickHouse queries",
        graph_category: "clickhouse",
        graph_info: "Values received from ClickHouse system.events table",
        graph_vlabel: "queries / second",
        graph_period: "second",
        graph_args: "--lower-limit 0",
        metrics: []MetricConfig{
            MetricConfig{
                id: "select",
                label: "Selects",
                metric_type: "DERIVE",
                draw_type: "AREA",
                color: "COLOUR0",
                clickhouseEvent: "SelectQuery",
            },
            MetricConfig{
                id: "insert",
                label: "Inserts",
                metric_type: "DERIVE",
                draw_type: "STACK",
                color: "COLOUR1",
                clickhouseEvent: "InsertQuery",
            },
        },
    },
    "files": Widget{
        graph_title: "ClickHouse files",
        graph_category: "clickhouse",
        graph_info: "Values received from ClickHouse system.events table",
        graph_vlabel: "operations / second",
        graph_period: "second",
        graph_args: "--lower-limit 0",
        metrics: []MetricConfig{
            MetricConfig{
                id: "file_open",
                label: "Opens",
                metric_type: "DERIVE",
                draw_type: "AREA",
                color: "COLOUR0",
                clickhouseEvent: "FileOpen",
            },
            MetricConfig{
                id: "seek",
                label: "Seeks",
                metric_type: "DERIVE",
                draw_type: "STACK",
                color: "COLOUR1",
                clickhouseEvent: "Seek",
            },
        },
    },
    "cache": Widget{
        graph_title: "ClickHouse cache",
        graph_category: "clickhouse",
        graph_info: "Values received from ClickHouse system.events table",
        graph_vlabel: "per second",
        graph_period: "second",
        graph_args: "-u 100 -l 0 -r --base 1000",
        metrics: []MetricConfig{
            MetricConfig{
                id: "hits",
                label: "hit ratio",
                metric_type: "GAUGE",
                draw_type: "AREA",
                color: "COLOUR0",
                clickhouseEvent: "MarkCacheHits",
            },
            MetricConfig{
                id: "misses",
                label: "miss ratio",
                metric_type: "GAUGE",
                draw_type: "STACK",
                color: "COLOUR1",
                clickhouseEvent: "MarkCacheMisses",
            },
        },
    },
    "zookeeper": Widget{
        graph_title: "ClickHouse Zookeeper transactions",
        graph_category: "clickhouse",
        graph_info: "Values received from ClickHouse system.events table",
        graph_vlabel: "operations / second",
        graph_period: "second",
        graph_args: "--lower-limit 0",
        metrics: []MetricConfig{
            MetricConfig{
                id: "ZooKeeperGetChildren",
                label: "ZooKeeperGetChildren",
                metric_type: "DERIVE",
                draw_type: "AREA",
                color: "COLOUR0",
                clickhouseEvent: "ZooKeeperGetChildren",
            },
            MetricConfig{
                id: "ZooKeeperCreate",
                label: "ZooKeeperCreate",
                metric_type: "DERIVE",
                draw_type: "STACK",
                color: "COLOUR1",
                clickhouseEvent: "ZooKeeperCreate",
            },
            MetricConfig{
                id: "ZooKeeperRemove",
                label: "ZooKeeperRemove",
                metric_type: "DERIVE",
                draw_type: "STACK",
                color: "COLOUR2",
                clickhouseEvent: "ZooKeeperRemove",
            },
            MetricConfig{
                id: "ZooKeeperExists",
                label: "ZooKeeperExists",
                metric_type: "DERIVE",
                draw_type: "STACK",
                color: "COLOUR3",
                clickhouseEvent: "ZooKeeperExists",
            },
            MetricConfig{
                id: "ZooKeeperGet",
                label: "ZooKeeperGet",
                metric_type: "DERIVE",
                draw_type: "STACK",
                color: "COLOUR4",
                clickhouseEvent: "ZooKeeperGet",
            },
            MetricConfig{
                id: "ZooKeeperSet",
                label: "ZooKeeperSet",
                metric_type: "DERIVE",
                draw_type: "STACK",
                color: "COLOUR5",
                clickhouseEvent: "ZooKeeperSet",
            },
            MetricConfig{
                id: "ZooKeeperMulti",
                label: "ZooKeeperMulti",
                metric_type: "DERIVE",
                draw_type: "STACK",
                color: "COLOUR6",
                clickhouseEvent: "ZooKeeperMulti",
            },
            MetricConfig{
                id: "ZooKeeperExceptions",
                label: "ZooKeeperExceptions",
                metric_type: "DERIVE",
                draw_type: "STACK",
                color: "COLOUR7",
                clickhouseEvent: "ZooKeeperExceptions",
            },
        },
    },
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"github.com/BurntSushi/toml"
	"log"
	"os"
)

const usageString = "Usage: red-alert [config-file]"

type Config struct {
	PollingInterval    int    // Poll New Relic every N seconds
	DurationForAverage int    // Calculate average response time based on the last N minutes
	ApiKey             string // The New Relic API key
	ApplicationId      string // The New Relic application ID
}

var defaultConfig Config = Config{
	30, // PollingInterval
	15, // DurationForAverage
	"", // ApiKey
	"", // ApplicationId
}

// TODO: Allow setting the ApiKey and AppId, at least, with command-line flags.
func GetConfig() Config {
	config := defaultConfig
	var logfile string

	switch len(flag.Args()) {
	case 0:
		logfile = os.Getenv("HOME") + "/.red-alert.config"
	case 1:
		logfile = flag.Arg(0)
	default:
		log.Fatal(usageString)
	}

	if _, err := toml.DecodeFile(logfile, &config); err != nil {
		log.Fatalf("Couldn't read config file %s: %s", logfile, err)
	}

	if config.ApiKey == "" {
		log.Fatal("No ApiKey found in the config file!")
	}
	if config.ApplicationId == "" {
		log.Fatal("No ApplicationId found in the config file!")
	}
	return config
}
<commit_msg>Config ints should be unsigned.<commit_after>package main

import (
	"flag"
	"github.com/BurntSushi/toml"
	"log"
	"os"
)

const usageString = "Usage: red-alert [config-file]"

type Config struct {
	PollingInterval    uint   // Poll New Relic every N seconds
	DurationForAverage uint   // Calculate average response time based on the last N minutes
	ApiKey             string // The New Relic API key
	ApplicationId      string // The New Relic application ID
}

var defaultConfig Config = Config{
	30, // PollingInterval
	15, // DurationForAverage
	"", // ApiKey
	"", // ApplicationId
}

// TODO: Allow setting the ApiKey and AppId, at least, with command-line flags.
func GetConfig() Config {
	config := defaultConfig
	var logfile string

	switch len(flag.Args()) {
	case 0:
		logfile = os.Getenv("HOME") + "/.red-alert.config"
	case 1:
		logfile = flag.Arg(0)
	default:
		log.Fatal(usageString)
	}

	if _, err := toml.DecodeFile(logfile, &config); err != nil {
		log.Fatalf("Couldn't read config file %s: %s", logfile, err)
	}

	if config.ApiKey == "" {
		log.Fatal("No ApiKey found in the config file!")
	}
	if config.ApplicationId == "" {
		log.Fatal("No ApplicationId found in the config file!")
	}
	return config
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/gorilla/websocket"

	"github.com/satori/go.uuid"
)

// Handles a websocket connection from a procedure caller
func handleClientWS(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		// CWS stands for client WS
		log.Println("CWS:", err)
		return
	}

	// Generate a UID to identify this client
	clientUID := uuid.NewV4()
	log.Println("CWS: Client connected:", r.RemoteAddr, clientUID)

	// Create a return channel for this client
	// XXX: Hard code return channel size 50: if more than 50 outputs are received before dequeued to client,
	// outputs will be discarded
	retChannel := make(chan *ResponseObject, 50)
	retChLock.Lock()
	retChMap[clientUID] = retChannel
	retChLock.Unlock()

	defer func() {
		c.Close()
		// Delete return channel for this client
		retChLock.Lock()
		close(retChMap[clientUID])
		delete(retChMap, clientUID)
		retChLock.Unlock()
		log.Println("CWS: Client disconnected:", r.RemoteAddr)
	}()

	// Start the output routine: listen on the return channel and send to client when outputs are received
	go deliverResponseRoutine(retChannel, c)

	for {
		// Read input message from client
		_, data, err := c.ReadMessage()
		if err != nil {
			log.Println("CWS:", err)
			break
		}
		var reqObject RequestObject
		err = json.Unmarshal(data, &reqObject)
		if err != nil {
			log.Println("CWS: Error unmarshalling message!", err, string(data))
		}

		// Attach requester's UID to the request input
		reqObject.RID = clientUID.String()
		// Clear the TID before enqueuing
		targetID := reqObject.TID
		reqObject.TID = ""

		reqChLock.RLock()
		reqChannel := reqChMap[targetID]
		reqChLock.RUnlock()
		select {
		case reqChannel <- &reqObject:
			log.Println("WSC: Sent:", reqObject)
		default:
			// Fails to send the input because component's chan is full, or its channel does not exist
			log.Println("CWS: Input channel unavailable. Discarding:", reqObject)
			// TODO: Server respond with an error message
		}

	}
}

func deliverResponseRoutine(retChannel chan *ResponseObject, c *websocket.Conn) {
	for {
		retMsg, ok := <-retChannel
		if ok {
			// A response object has arrived for this requester!
			errWrite := c.WriteJSON(retMsg)
			if errWrite != nil {
				log.Println("CWS:", errWrite)
			}
			log.Println("CWS: Response returned:", retMsg)
		} else {
			log.Println("CWS: Return channel closed!")
			break
		}
	}
}
<commit_msg>Typo in log<commit_after>package main

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/gorilla/websocket"

	"github.com/satori/go.uuid"
)

// Handles a websocket connection from a procedure caller
func handleClientWS(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		// CWS stands for client WS
		log.Println("CWS:", err)
		return
	}

	// Generate a UID to identify this client
	clientUID := uuid.NewV4()
	log.Println("CWS: Client connected:", r.RemoteAddr, clientUID)

	// Create a return channel for this client
	// XXX: Hard code return channel size 50: if more than 50 outputs are received before dequeued to client,
	// outputs will be discarded
	retChannel := make(chan *ResponseObject, 50)
	retChLock.Lock()
	retChMap[clientUID] = retChannel
	retChLock.Unlock()

	defer func() {
		c.Close()
		// Delete return channel for this client
		retChLock.Lock()
		close(retChMap[clientUID])
		delete(retChMap, clientUID)
		retChLock.Unlock()
		log.Println("CWS: Client disconnected:", r.RemoteAddr)
	}()

	// Start the output routine: listen on the return channel and send to client when outputs are received
	go deliverResponseRoutine(retChannel, c)

	for {
		// Read input message from client
		_, data, err := c.ReadMessage()
		if err != nil {
			log.Println("CWS:", err)
			break
		}
		var reqObject RequestObject
		err = json.Unmarshal(data, &reqObject)
		if err != nil {
			log.Println("CWS: Error unmarshalling message!", err, string(data))
		}

		// Attach requester's UID to the request input
		reqObject.RID = clientUID.String()
		// Clear the TID before enqueuing
		targetID := reqObject.TID
		reqObject.TID = ""

		reqChLock.RLock()
		reqChannel := reqChMap[targetID]
		reqChLock.RUnlock()
		select {
		case reqChannel <- &reqObject:
			log.Println("CWS: Sent:", reqObject)
		default:
			// Fails to send the input because component's chan is full, or its channel does not exist
			log.Println("CWS: Input channel unavailable. Discarding:", reqObject)
			// TODO: Server respond with an error message
		}

	}
}

func deliverResponseRoutine(retChannel chan *ResponseObject, c *websocket.Conn) {
	for {
		retMsg, ok := <-retChannel
		if ok {
			// A response object has arrived for this requester!
			errWrite := c.WriteJSON(retMsg)
			if errWrite != nil {
				log.Println("CWS:", errWrite)
			}
			log.Println("CWS: Response returned:", retMsg)
		} else {
			log.Println("CWS: Return channel closed!")
			break
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"gnd.la/config"
	"gnd.la/log"
	"gnd.la/util/textutil"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
)

type Config struct {
	File        string
	Command     string
	Name        string
	Dir         string
	Environment map[string]string
	User        string
	Group       string
	Priority    int
	Err         error
}

func (c *Config) Cmd() (*exec.Cmd, error) {
	if c.Err != nil {
		return nil, c.Err
	}
	if c.Command == "" {
		return nil, fmt.Errorf("no command")
	}
	fields, err := textutil.SplitFields(c.Command, " ")
	if err != nil {
		return nil, err
	}
	if !filepath.IsAbs(fields[0]) {
		p, err := exec.LookPath(fields[0])
		if err != nil {
			return nil, err
		}
		fields[0] = p
	}
	cmd := &exec.Cmd{Path: fields[0], Args: fields, Dir: c.Dir}
	for k, v := range c.Environment {
		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
	}
	for _, v := range os.Environ() {
		if p := strings.IndexByte(v, '='); p >= 0 {
			k := v[:p]
			if _, ok := c.Environment[k]; !ok {
				cmd.Env = append(cmd.Env, v)
			}
		}
	}
	var uid uint32
	var gid uint32
	if c.Group != "" {
		if g := getGroupId(c.Group); g > 0 {
			gid = uint32(g)
		} else {
			return nil, fmt.Errorf("invalid group %q", c.Group)
		}
	}
	if c.User != "" {
		u, err := user.Lookup(c.User)
		if err != nil {
			return nil, err
		}
		ui, _ := strconv.Atoi(u.Uid)
		uid = uint32(ui)
		if gid == 0 {
			gi, _ := strconv.Atoi(u.Gid)
			gid = uint32(gi)
		}
	}
	if uid != 0 || gid != 0 {
		attr := &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: uid, Gid: gid}}
		cmd.SysProcAttr = attr
	}
	return cmd, nil
}

func (c *Config) ServiceName() string {
	if c.Name != "" {
		return c.Name
	}
	if fields, err := textutil.SplitFields(c.Command, " "); err == nil && len(fields) > 0 {
		return filepath.Base(fields[0])
	}
	return c.File
}

func ParseConfig(filename string) *Config {
	cfg := &Config{File: filename}
	err := config.ParseFile(filepath.Join(*configDir, filename), cfg)
	cfg.Err = err
	return cfg
}

func ParseConfigs() ([]*Config, error) {
	files, err := ioutil.ReadDir(*configDir)
	if err != nil {
		return nil, fmt.Errorf("error reading config directory %s: %s", *configDir, err)
	}
	var configs []*Config
	for _, v := range files {
		name := v.Name()
		if name[0] == '.' {
			continue
		}
		cfg := ParseConfig(name)
		log.Debugf("Parsed config %s: %+v", name, cfg)
		configs = append(configs, cfg)
	}
	return configs, nil
}
<commit_msg>Set dir to the binary dir if it's empty<commit_after>package main

import (
	"fmt"
	"gnd.la/config"
	"gnd.la/log"
	"gnd.la/util/textutil"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
)

type Config struct {
	File        string
	Command     string
	Name        string
	Dir         string
	Environment map[string]string
	User        string
	Group       string
	Priority    int
	Err         error
}

func (c *Config) Cmd() (*exec.Cmd, error) {
	if c.Err != nil {
		return nil, c.Err
	}
	if c.Command == "" {
		return nil, fmt.Errorf("no command")
	}
	fields, err := textutil.SplitFields(c.Command, " ")
	if err != nil {
		return nil, err
	}
	if !filepath.IsAbs(fields[0]) {
		p, err := exec.LookPath(fields[0])
		if err != nil {
			return nil, err
		}
		fields[0] = p
	}
	dir := c.Dir
	if dir == "" {
		dir = filepath.Base(fields[0])
	}
	cmd := &exec.Cmd{Path: fields[0], Args: fields, Dir: dir}
	for k, v := range c.Environment {
		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
	}
	for _, v := range os.Environ() {
		if p := strings.IndexByte(v, '='); p >= 0 {
			k := v[:p]
			if _, ok := c.Environment[k]; !ok {
				cmd.Env = append(cmd.Env, v)
			}
		}
	}
	var uid uint32
	var gid uint32
	if c.Group != "" {
		if g := getGroupId(c.Group); g > 0 {
			gid = uint32(g)
		} else {
			return nil, fmt.Errorf("invalid group %q", c.Group)
		}
	}
	if c.User != "" {
		u, err := user.Lookup(c.User)
		if err != nil {
			return nil, err
		}
		ui, _ := strconv.Atoi(u.Uid)
		uid = uint32(ui)
		if gid == 0 {
			gi, _ := strconv.Atoi(u.Gid)
			gid = uint32(gi)
		}
	}
	if uid != 0 || gid != 0 {
		attr := &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: uid, Gid: gid}}
		cmd.SysProcAttr = attr
	}
	return cmd, nil
}

func (c *Config) ServiceName() string {
	if c.Name != "" {
		return c.Name
	}
	if fields, err := textutil.SplitFields(c.Command, " "); err == nil && len(fields) > 0 {
		return filepath.Base(fields[0])
	}
	return c.File
}

func ParseConfig(filename string) *Config {
	cfg := &Config{File: filename}
	err := config.ParseFile(filepath.Join(*configDir, filename), cfg)
	cfg.Err = err
	return cfg
}

func ParseConfigs() ([]*Config, error) {
	files, err := ioutil.ReadDir(*configDir)
	if err != nil {
		return nil, fmt.Errorf("error reading config directory %s: %s", *configDir, err)
	}
	var configs []*Config
	for _, v := range files {
		name := v.Name()
		if name[0] == '.' {
			continue
		}
		cfg := ParseConfig(name)
		log.Debugf("Parsed config %s: %+v", name, cfg)
		configs = append(configs, cfg)
	}
	return configs, nil
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"io/ioutil"
	"strings"

	"gopkg.in/yaml.v2"
)

const (
	defaultConfigFileName = "_config.yaml"

	defaultTitle       = "My Gallery"
	defaultDescription = "My Gallery Description"

	skipPrefix       = "_"
	hiddenFilePrefix = "."
)

type Config struct {
	Title       string           `yaml:"title"`
	Description string           `yaml:"description"`
	Author      string           `yaml:"author,omitempty"`
	Analytics   string           `yaml:"analytics"`
	Content     map[string]*File `yaml:"content"`
}

type File struct {
	Name        string   `yaml:"name"`
	Description string   `yaml:"description"`
	Tags        []string `yaml:"tags,omitempty"`
	Exif        bool     `yaml:"exif,omitempty"`
	Permalink   string   `yaml:"permalink,omitempty"`
}

func newConfig() *Config {
	return &Config{
		Title:       defaultTitle,
		Description: defaultDescription,
		Content:     make(map[string]*File),
	}
}

func (c *Config) load(r io.Reader) error {
	var data []byte
	var err error
	if data, err = ioutil.ReadAll(r); err != nil {
		return err
	}
	return yaml.Unmarshal(data, c)
}

func (c *Config) save(w io.Writer) error {
	d, err := yaml.Marshal(c)
	if err != nil {
		return err
	}
	_, err = w.Write(d)
	return err
}

func (c *Config) generate(dirname string) error {
	files, err := ioutil.ReadDir(dirname)
	if err != nil {
		return err
	}
	for _, fileInfo := range files {
		if strings.HasPrefix(fileInfo.Name(), skipPrefix) || strings.HasPrefix(fileInfo.Name(), hiddenFilePrefix) {
			continue
		}
		if _, ok := c.Content[fileInfo.Name()]; ok {
			// Skip existing setup
			continue
		}
		c.Content[fileInfo.Name()] = &File{
			Name:        fileInfo.Name(),
			Description: "",
			Exif:        false,
		}
	}
	return nil
}
<commit_msg>Removed default exif<commit_after>package main

import (
	"io"
	"io/ioutil"
	"strings"

	"gopkg.in/yaml.v2"
)

const (
	defaultConfigFileName = "_config.yaml"

	defaultTitle       = "My Gallery"
	defaultDescription = "My Gallery Description"

	skipPrefix       = "_"
	hiddenFilePrefix = "."
)

type Config struct {
	Title       string           `yaml:"title"`
	Description string           `yaml:"description"`
	Author      string           `yaml:"author,omitempty"`
	Analytics   string           `yaml:"analytics"`
	Content     map[string]*File `yaml:"content"`
}

type File struct {
	Name        string   `yaml:"name"`
	Description string   `yaml:"description"`
	Tags        []string `yaml:"tags,omitempty"`
	Exif        bool     `yaml:"exif,omitempty"`
	Permalink   string   `yaml:"permalink,omitempty"`
}

func newConfig() *Config {
	return &Config{
		Title:       defaultTitle,
		Description: defaultDescription,
		Content:     make(map[string]*File),
	}
}

func (c *Config) load(r io.Reader) error {
	var data []byte
	var err error
	if data, err = ioutil.ReadAll(r); err != nil {
		return err
	}
	return yaml.Unmarshal(data, c)
}

func (c *Config) save(w io.Writer) error {
	d, err := yaml.Marshal(c)
	if err != nil {
		return err
	}
	_, err = w.Write(d)
	return err
}

func (c *Config) generate(dirname string) error {
	files, err := ioutil.ReadDir(dirname)
	if err != nil {
		return err
	}
	for _, fileInfo := range files {
		if strings.HasPrefix(fileInfo.Name(), skipPrefix) || strings.HasPrefix(fileInfo.Name(), hiddenFilePrefix) {
			continue
		}
		if _, ok := c.Content[fileInfo.Name()]; ok {
			// Skip existing setup
			continue
		}
		c.Content[fileInfo.Name()] = &File{
			Name:        fileInfo.Name(),
			Description: "",
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"regexp"

	"github.com/ReshNesh/go-colorful"
	"gopkg.in/yaml.v1"
)

const (
	// LRU = Least recently used
	LRU = "LRU"
	// LFU = Least frequently used
	LFU = "LFU"
)

const (
	defaultThrottlingRate             = 60 // Requests per min
	defaultCacheLimit                 = 0  // No. of bytes
	defaultJpegQuality                = 75
	defaultUploadMaxFileSize          = 5 * 1024 * 1024 // No. of bytes
	defaultAllowCustomTransformations = true
	defaultAllowCustomScale           = true
	defaultAsyncUploads               = false
	defaultAuthorisedGet              = false
	defaultAuthorisedUpload           = false
	defaultLocalPath                  = "local-images"
	defaultCacheStrategy              = LRU
	defaultFont                       = "fonts/DejaVuSans.ttf"
)

var (
	// Config is a global configuration object
	Config Configuration
)

// Configuration specifies server configuration options
type Configuration struct {
	throttlingRate, cacheLimit, jpegQuality, uploadMaxFileSize                                  int
	allowCustomTransformations, allowCustomScale, asyncUploads, authorisedGet, authorisedUpload bool
	localPath, cacheStrategy                                                                    string
	transformations                                                                             map[string]Transformation
	eagerTransformations                                                                        []Transformation
}

func configInit(configFilePath string) error {
	Config = Configuration{defaultThrottlingRate, defaultCacheLimit, defaultJpegQuality, defaultUploadMaxFileSize, defaultAllowCustomTransformations, defaultAllowCustomScale, defaultAsyncUploads, defaultAuthorisedGet, defaultAuthorisedUpload, defaultLocalPath, defaultCacheStrategy, make(map[string]Transformation), make([]Transformation, 0)}

	if configFilePath == "" {
		return nil
	}

	data, err := ioutil.ReadFile(configFilePath)
	if err != nil {
		return err
	}

	m := make(map[interface{}]interface{})
	err = yaml.Unmarshal([]byte(data), &m)

	throttlingRate, ok := m["throttling-rate"].(int)
	if ok && throttlingRate >= 0 {
		Config.throttlingRate = throttlingRate
	}

	jpegQuality, ok := m["jpeg-quality"].(int)
	if ok && jpegQuality >= 1 && jpegQuality <= 100 {
		Config.jpegQuality = jpegQuality
	}

	uploadMaxFileSize, ok := m["upload-max-file-size"].(int)
	if ok && uploadMaxFileSize > 0 {
		Config.uploadMaxFileSize = uploadMaxFileSize
	}

	allowCustomTransformations, ok := m["allow-custom-transformations"].(bool)
	if ok {
		Config.allowCustomTransformations = allowCustomTransformations
	}

	allowCustomScale, ok := m["allow-custom-scale"].(bool)
	if ok {
		Config.allowCustomScale = allowCustomScale
	}

	asyncUploads, ok := m["async-uploads"].(bool)
	if ok {
		Config.asyncUploads = asyncUploads
	}

	authorisation, ok := m["authorisation"].(map[interface{}]interface{})
	if ok {
		get, ok := authorisation["get"].(bool)
		if ok {
			Config.authorisedGet = get
		}
		upload, ok := authorisation["upload"].(bool)
		if ok {
			Config.authorisedUpload = upload
		}
	}

	localPath, ok := m["local-path"].(string)
	if ok {
		Config.localPath = localPath
	}

	cache, ok := m["cache"].(map[interface{}]interface{})
	if ok {
		limit, ok := cache["limit"].(int)
		if ok {
			Config.cacheLimit = limit
		}

		strategy, ok := cache["strategy"].(string)
		if ok && (strategy == LRU || strategy == LFU) {
			Config.cacheStrategy = strategy
		}
	}

	transformations, ok := m["transformations"].([]interface{})
	if !ok {
		return nil
	}

	for _, transformationInterface := range transformations {
		transformation, ok := transformationInterface.(map[interface{}]interface{})
		if !ok {
			continue
		}

		parametersStr, ok := transformation["parameters"].(string)
		if !ok {
			continue
		}

		params, err := parseParameters(parametersStr)
		if err != nil {
			return fmt.Errorf("invalid transformation parameters: %s (%s)", parametersStr, err)
		}

		name, ok := transformation["name"].(string)
		if !ok {
			continue
		}
		if !isValidTransformationName(name) {
			return fmt.Errorf("invalid transformation name: %s", name)
		}

		t := Transformation{&params, nil, make([]*Text, 0)}

		watermarkMap, ok := transformation["watermark"].(map[interface{}]interface{})
		if ok {
			imagePath, ok := watermarkMap["source"].(string)
			if !ok {
				return fmt.Errorf("a watermark needs to have a source specified")
			}
			// x and y will default to 0 if not found in config
			x := watermarkMap["x-pos"].(int)
			y := watermarkMap["y-pos"].(int)
			t.watermark = &Watermark{imagePath, x, y}
		}

		texts, ok := transformation["text"].([]interface{})
		if ok {

			for _, textMap := range texts {
				text, ok := textMap.(map[interface{}]interface{})
				if !ok {
					continue
				}

				content, ok := text["content"].(string)

				// x and y will default to 0 if not found in config
				x := text["x-pos"].(int)
				y := text["y-pos"].(int)

				colorStr, ok := text["color"].(string)
				if !ok {
					return fmt.Errorf("text needs to have a color specified")
				}
				color, err := colorful.Hex(colorStr)
				if err != nil {
					return err
				}

				font, ok := text["font"].(string)
				if !ok {
					font = defaultFont
				}
				//TODO - check that the file exists

				size, ok := text["size"].(int)
				if !ok {
					return fmt.Errorf("%v is not a valid size", text["size"])
				}
				if size < 1 {
					return fmt.Errorf("size needs to be at least 1")
				}

				t.texts = append(t.texts, &Text{content, font, x, y, size, color})
			}
		}

		Config.transformations[name] = t

		eager, ok := transformation["eager"].(bool)
		if ok && eager {
			Config.eagerTransformations = append(Config.eagerTransformations, t)
		}
	}

	return nil
}

var (
	transformationNameConfigRe = regexp.MustCompile("^([0-9A-Za-z-]+)$")
)

func isValidTransformationName(name string) bool {
	return transformationNameConfigRe.MatchString(name)
}
<commit_msg>Checking that a font file exists<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"regexp"

	"github.com/ReshNesh/go-colorful"
	"gopkg.in/yaml.v1"
)

const (
	// LRU = Least recently used
	LRU = "LRU"
	// LFU = Least frequently used
	LFU = "LFU"
)

const (
	defaultThrottlingRate             = 60 // Requests per min
	defaultCacheLimit                 = 0  // No. of bytes
	defaultJpegQuality                = 75
	defaultUploadMaxFileSize          = 5 * 1024 * 1024 // No. of bytes
	defaultAllowCustomTransformations = true
	defaultAllowCustomScale           = true
	defaultAsyncUploads               = false
	defaultAuthorisedGet              = false
	defaultAuthorisedUpload           = false
	defaultLocalPath                  = "local-images"
	defaultCacheStrategy              = LRU
	defaultFontPath                   = "fonts/DejaVuSans.ttf"
)

var (
	// Config is a global configuration object
	Config Configuration
)

// Configuration specifies server configuration options
type Configuration struct {
	throttlingRate, cacheLimit, jpegQuality, uploadMaxFileSize                                  int
	allowCustomTransformations, allowCustomScale, asyncUploads, authorisedGet, authorisedUpload bool
	localPath, cacheStrategy                                                                    string
	transformations                                                                             map[string]Transformation
	eagerTransformations                                                                        []Transformation
}

func configInit(configFilePath string) error {
	Config = Configuration{defaultThrottlingRate, defaultCacheLimit, defaultJpegQuality, defaultUploadMaxFileSize, defaultAllowCustomTransformations, defaultAllowCustomScale, defaultAsyncUploads, defaultAuthorisedGet, defaultAuthorisedUpload, defaultLocalPath, defaultCacheStrategy, make(map[string]Transformation), make([]Transformation, 0)}

	if configFilePath == "" {
		return nil
	}

	data, err := ioutil.ReadFile(configFilePath)
	if err != nil {
		return err
	}

	m := make(map[interface{}]interface{})
	err = yaml.Unmarshal([]byte(data), &m)

	throttlingRate, ok := m["throttling-rate"].(int)
	if ok && throttlingRate >= 0 {
		Config.throttlingRate = throttlingRate
	}

	jpegQuality, ok := m["jpeg-quality"].(int)
	if ok && jpegQuality >= 1 && jpegQuality <= 100 {
		Config.jpegQuality = jpegQuality
	}

	uploadMaxFileSize, ok := m["upload-max-file-size"].(int)
	if ok && uploadMaxFileSize > 0 {
		Config.uploadMaxFileSize = uploadMaxFileSize
	}

	allowCustomTransformations, ok := m["allow-custom-transformations"].(bool)
	if ok {
		Config.allowCustomTransformations = allowCustomTransformations
	}

	allowCustomScale, ok := m["allow-custom-scale"].(bool)
	if ok {
		Config.allowCustomScale = allowCustomScale
	}

	asyncUploads, ok := m["async-uploads"].(bool)
	if ok {
		Config.asyncUploads = asyncUploads
	}

	authorisation, ok := m["authorisation"].(map[interface{}]interface{})
	if ok {
		get, ok := authorisation["get"].(bool)
		if ok {
			Config.authorisedGet = get
		}
		upload, ok := authorisation["upload"].(bool)
		if ok {
			Config.authorisedUpload = upload
		}
	}

	localPath, ok := m["local-path"].(string)
	if ok {
		Config.localPath = localPath
	}

	cache, ok := m["cache"].(map[interface{}]interface{})
	if ok {
		limit, ok := cache["limit"].(int)
		if ok {
			Config.cacheLimit = limit
		}

		strategy, ok := cache["strategy"].(string)
		if ok && (strategy == LRU || strategy == LFU) {
			Config.cacheStrategy = strategy
		}
	}

	transformations, ok := m["transformations"].([]interface{})
	if !ok {
		return nil
	}

	for _, transformationInterface := range transformations {
		transformation, ok := transformationInterface.(map[interface{}]interface{})
		if !ok {
			continue
		}

		parametersStr, ok := transformation["parameters"].(string)
		if !ok {
			continue
		}

		params, err := parseParameters(parametersStr)
		if err != nil {
			return fmt.Errorf("invalid transformation parameters: %s (%s)", parametersStr, err)
		}

		name, ok := transformation["name"].(string)
		if !ok {
			continue
		}
		if !isValidTransformationName(name) {
			return fmt.Errorf("invalid transformation name: %s", name)
		}

		t := Transformation{&params, nil, make([]*Text, 0)}

		watermarkMap, ok := transformation["watermark"].(map[interface{}]interface{})
		if ok {
			imagePath, ok := watermarkMap["source"].(string)
			if !ok {
				return fmt.Errorf("a watermark needs to have a source specified")
			}
			// x and y will default to 0 if not found in config
			x := watermarkMap["x-pos"].(int)
			y := watermarkMap["y-pos"].(int)
			t.watermark = &Watermark{imagePath, x, y}
		}

		texts, ok := transformation["text"].([]interface{})
		if ok {

			for _, textMap := range texts {
				text, ok := textMap.(map[interface{}]interface{})
				if !ok {
					continue
				}

				content, ok := text["content"].(string)

				// x and y will default to 0 if not found in config
				x := text["x-pos"].(int)
				y := text["y-pos"].(int)

				colorStr, ok := text["color"].(string)
				if !ok {
					return fmt.Errorf("text needs to have a color specified")
				}
				color, err := colorful.Hex(colorStr)
				if err != nil {
					return err
				}

				fontFilePath, ok := text["font"].(string)
				if !ok {
					fontFilePath = defaultFontPath
				}
				if _, err := os.Stat(fontFilePath); os.IsNotExist(err) {
					return fmt.Errorf("font does not exist: %s", fontFilePath)
				}

				size, ok := text["size"].(int)
				if !ok {
					return fmt.Errorf("%v is not a valid size", text["size"])
				}
				if size < 1 {
					return fmt.Errorf("size needs to be at least 1")
				}

				t.texts = append(t.texts, &Text{content, fontFilePath, x, y, size, color})
			}
		}

		Config.transformations[name] = t

		eager, ok := transformation["eager"].(bool)
		if ok && eager {
			Config.eagerTransformations = append(Config.eagerTransformations, t)
		}
	}

	return nil
}

var (
	transformationNameConfigRe = regexp.MustCompile("^([0-9A-Za-z-]+)$")
)

func isValidTransformationName(name string) bool {
	return transformationNameConfigRe.MatchString(name)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"io/ioutil"
	"log"

	"github.com/gin-gonic/gin"
)

type TodoAppConfig struct {
	HealthCheckTime int
	DBDriver        string
	DBConfig        map[string]string
	ReleaseMode     string
}

func readConfig(configFile string) (*TodoAppConfig, error) {
	file, err := ioutil.ReadFile(configFile)
	if err != nil {
		return &TodoAppConfig{
			DBDriver: "redis",
			DBConfig: map[string]string{},
		}, err
	}
	config := &TodoAppConfig{}
	json.Unmarshal(file, config)

	if config.DBDriver == "" {
		log.Println("Use redis as default")
		config.DBDriver = "redis"
	}

	if config.DBConfig == nil {
		config.DBConfig = map[string]string{}
	}

	if config.ReleaseMode == "" {
		config.ReleaseMode = gin.DebugMode
	}

	return config, nil
}
<commit_msg>Set ReleaseMode if config file not found<commit_after>package main

import (
	"encoding/json"
	"io/ioutil"
	"log"

	"github.com/gin-gonic/gin"
)

type TodoAppConfig struct {
	HealthCheckTime int
	DBDriver        string
	DBConfig        map[string]string
	ReleaseMode     string
}

func readConfig(configFile string) (*TodoAppConfig, error) {
	file, err := ioutil.ReadFile(configFile)
	if err != nil {
		return &TodoAppConfig{
			DBDriver:    "redis",
			DBConfig:    map[string]string{},
			ReleaseMode: gin.DebugMode,
		}, err
	}
	config := &TodoAppConfig{}
	json.Unmarshal(file, config)

	if config.DBDriver == "" {
		log.Println("Use redis as default")
		config.DBDriver = "redis"
	}

	if config.DBConfig == nil {
		config.DBConfig = map[string]string{}
	}

	if config.ReleaseMode == "" {
		config.ReleaseMode = gin.DebugMode
	}

	return config, err
}
<|endoftext|>


<commit_before>package cmd

import (
	"os"
	"strings"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
)

const (
	FlagAddr           = "addr"
	FlagAddrAPI        = "addr-api"
	FlagDatabaseDriver = "database-driver"
	FlagDatabaseDSN    = "database-dsn"
	FlagLogJson        = "log-json"
	FlagLogLevel       = "log-level"
	FlagSecret         = "secret"

	EnvAddr           = "GITPODS_ADDR"
	EnvAddrAPI        = "GITPODS_ADDR_API"
	EnvDatabaseDriver = "GITPODS_DATABASE_DRIVER"
	EnvDatabaseDSN    = "GITPODS_DATABASE_DSN"
	EnvLogJson        = "GITPODS_LOG_LEVEL"
	EnvLogLevel       = "GITPODS_LOG_JSON"
	EnvSecret         = "GITPODS_SECRET"
)

func NewLogger(json bool, loglevel string) log.Logger {
	var logger log.Logger

	if json {
		logger = log.NewJSONLogger(log.NewSyncWriter(os.Stdout))
	} else {
		logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
	}

	switch strings.ToLower(loglevel) {
	case "debug":
		logger = level.NewFilter(logger, level.AllowDebug())
	case "warn":
		logger = level.NewFilter(logger, level.AllowWarn())
	case "error":
		logger = level.NewFilter(logger, level.AllowError())
	default:
		logger = level.NewFilter(logger, level.AllowInfo())
	}

	return log.With(logger, "ts", log.DefaultTimestampUTC)
}
<commit_msg>Log the caller with the default cmd logger<commit_after>package cmd

import (
	"os"
	"strings"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
)

const (
	FlagAddr           = "addr"
	FlagAddrAPI        = "addr-api"
	FlagDatabaseDriver = "database-driver"
	FlagDatabaseDSN    = "database-dsn"
	FlagLogJson        = "log-json"
	FlagLogLevel       = "log-level"
	FlagSecret         = "secret"

	EnvAddr           = "GITPODS_ADDR"
	EnvAddrAPI        = "GITPODS_ADDR_API"
	EnvDatabaseDriver = "GITPODS_DATABASE_DRIVER"
	EnvDatabaseDSN    = "GITPODS_DATABASE_DSN"
	EnvLogJson        = "GITPODS_LOG_LEVEL"
	EnvLogLevel       = "GITPODS_LOG_JSON"
	EnvSecret         = "GITPODS_SECRET"
)

func NewLogger(json bool, loglevel string) log.Logger {
	var logger log.Logger

	if json {
		logger = log.NewJSONLogger(log.NewSyncWriter(os.Stdout))
	} else {
		logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
	}

	switch strings.ToLower(loglevel) {
	case "debug":
		logger = level.NewFilter(logger, level.AllowDebug())
	case "warn":
		logger = level.NewFilter(logger, level.AllowWarn())
	case "error":
		logger = level.NewFilter(logger, level.AllowError())
	default:
		logger = level.NewFilter(logger, level.AllowInfo())
	}

	return log.With(logger,
		"ts", log.DefaultTimestampUTC,
		"caller", log.DefaultCaller,
	)
}
<|endoftext|>