<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()
	defaultRPCKeyFile  = filepath.Join(btcdHomeDir, "rpc.key")
	defaultRPCCertFile = filepath.Join(btcdHomeDir, "rpc.cert")
)

// 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 or --proxy options are used without also specifying listen interfaces via --listen"`
	Listeners          []string      `long:"listen" description:"Add an interface/port to listen for connections (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"`
	RPCListeners       []string      `long:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 8334, testnet: 18334)"`
	RPCCert            string        `long:"rpccert" description:"File containing the certificate file"`
	RPCKey             string        `long:"rpckey" description:"File containing the certificate key"`
	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)
}

// 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,
		MaxPeers:    defaultMaxPeers,
		BanDuration: defaultBanDuration,
		ConfigFile:  defaultConfigFile,
		DataDir:     defaultDataDir,
		DbType:      defaultDbType,
		RPCKey:      defaultRPCKeyFile,
		RPCCert:     defaultRPCCertFile,
	}

	// 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)
	}

	// 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
	}

	if len(cfg.RPCListeners) == 0 {
		cfg.RPCListeners = []string{
			net.JoinHostPort("", activeNetParams.rpcPort),
		}
	}

	// Add default port to all listener addresses if needed and remove
	// duplicate addresses.
	cfg.Listeners = normalizeAddresses(cfg.Listeners,
		activeNetParams.listenPort)

	// Add default port to all rpc listener addresses if needed and remove
	// duplicate addresses.
	cfg.RPCListeners = normalizeAddresses(cfg.RPCListeners,
		activeNetParams.rpcPort)

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

	return &cfg, remainingArgs, nil
}
<commit_msg>Create the home directory if it doesn't exist.<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()
	defaultRPCKeyFile  = filepath.Join(btcdHomeDir, "rpc.key")
	defaultRPCCertFile = filepath.Join(btcdHomeDir, "rpc.cert")
)

// 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 or --proxy options are used without also specifying listen interfaces via --listen"`
	Listeners          []string      `long:"listen" description:"Add an interface/port to listen for connections (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"`
	RPCListeners       []string      `long:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 8334, testnet: 18334)"`
	RPCCert            string        `long:"rpccert" description:"File containing the certificate file"`
	RPCKey             string        `long:"rpckey" description:"File containing the certificate key"`
	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)
}

// 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,
		MaxPeers:    defaultMaxPeers,
		BanDuration: defaultBanDuration,
		ConfigFile:  defaultConfigFile,
		DataDir:     defaultDataDir,
		DbType:      defaultDbType,
		RPCKey:      defaultRPCKeyFile,
		RPCCert:     defaultRPCCertFile,
	}

	// Create the home directory if it doesn't already exist.
	err := os.MkdirAll(btcdHomeDir, 0700)
	if err != nil {
		log.Errorf("%v", err)
		os.Exit(-1)
	}

	// 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)
	}

	// 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
	}

	if len(cfg.RPCListeners) == 0 {
		cfg.RPCListeners = []string{
			net.JoinHostPort("", activeNetParams.rpcPort),
		}
	}

	// Add default port to all listener addresses if needed and remove
	// duplicate addresses.
	cfg.Listeners = normalizeAddresses(cfg.Listeners,
		activeNetParams.listenPort)

	// Add default port to all rpc listener addresses if needed and remove
	// duplicate addresses.
	cfg.RPCListeners = normalizeAddresses(cfg.RPCListeners,
		activeNetParams.rpcPort)

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

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


<commit_before>package tfjson

import (
	"encoding/json"
	"errors"
)

// Config represents the complete configuration source.
type Config struct {
	// A map of all provider instances across all modules in the
	// configuration.
	//
	// The index for this field is opaque and should not be parsed. Use
	// the individual fields in ProviderConfig to discern actual data
	// about the provider such as name, alias, or defined module.
	ProviderConfigs map[string]*ProviderConfig `json:"provider_config,omitempty"`

	// The root module in the configuration. Any child modules descend
	// off of here.
	RootModule *ConfigModule `json:"root_module,omitempty"`
}

// Validate checks to ensure that the config is present.
func (c *Config) Validate() error {
	if c == nil {
		return errors.New("config is nil")
	}

	return nil
}

func (c *Config) UnmarshalJSON(b []byte) error {
	type rawConfig Config
	var config rawConfig

	err := json.Unmarshal(b, &config)
	if err != nil {
		return err
	}

	*c = *(*Config)(&config)

	return c.Validate()
}

// ProviderConfig describes a provider configuration instance.
type ProviderConfig struct {
	// The name of the provider, ie: "aws".
	Name string `json:"name,omitempty"`

	// The alias of the provider, ie: "us-east-1".
	Alias string `json:"alias,omitempty"`

	// The address of the module the provider is declared in.
	ModuleAddress string `json:"module_address,omitempty"`

	// Any non-special configuration values in the provider, indexed by
	// key.
	Expressions map[string]*Expression `json:"expressions,omitempty"`

	// The defined version constraint for this provider.
	VersionConstraint string `json:"version_constraint,omitempty"`
}

// ConfigModule describes a module in Terraform configuration.
type ConfigModule struct {
	// The outputs defined in the module.
	Outputs map[string]*ConfigOutput `json:"outputs,omitempty"`

	// The resources defined in the module.
	Resources []*ConfigResource `json:"resources,omitempty"`

	// Any "module" stanzas within the specific module.
	ModuleCalls map[string]*ModuleCall `json:"module_calls,omitempty"`

	// The variables defined in the module.
	Variables map[string]*ConfigVariable `json:"variables,omitempty"`
}

// ConfigOutput defines an output as defined in configuration.
type ConfigOutput struct {
	// Indicates whether or not the output was marked as sensitive.
	Sensitive bool `json:"sensitive,omitempty"`

	// The defined value of the output.
	Expression *Expression `json:"expression,omitempty"`

	// The defined description of this output.
	Description string `json:"description,omitempty"`

	// The defined dependencies tied to this output.
	DependsOn []string `json:"depends_on,omitempty"`
}

// ConfigResource is the configuration representation of a resource.
type ConfigResource struct {
	// The address of the resource relative to the module that it is
	// in.
	Address string `json:"address,omitempty"`

	// The resource mode.
	Mode ResourceMode `json:"mode,omitempty"`

	// The type of resource, ie: "null_resource" in
	// "null_resource.foo".
	Type string `json:"type,omitempty"`

	// The name of the resource, ie: "foo" in "null_resource.foo".
	Name string `json:"name,omitempty"`

	// An opaque key representing the provider configuration this
	// module uses. Note that there are more than one circumstance that
	// this key will not match what is found in the ProviderConfigs
	// field in the root Config structure, and as such should not be
	// relied on for that purpose.
	ProviderConfigKey string `json:"provider_config_key,omitempty"`

	// The list of provisioner defined for this configuration. This
	// will be nil if no providers are defined.
	Provisioners []*ConfigProvisioner `json:"provisioners,omitempty"`

	// Any non-special configuration values in the resource, indexed by
	// key.
	Expressions map[string]*Expression `json:"expressions,omitempty"`

	// The resource's configuration schema version. With access to the
	// specific Terraform provider for this resource, this can be used
	// to determine the correct schema for the configuration data
	// supplied in Expressions.
	SchemaVersion uint64 `json:"schema_version"`

	// The expression data for the "count" value in the resource.
	CountExpression *Expression `json:"count_expression,omitempty"`

	// The expression data for the "for_each" value in the resource.
	ForEachExpression *Expression `json:"for_each_expression,omitempty"`

	// The contents of the "depends_on" config directive, which
	// declares explicit dependencies for this resource.
	DependsOn []string `json:"depends_on,omitempty"`
}

// ConfigVariable defines a variable as defined in configuration.
type ConfigVariable struct {
	// The defined default value of the variable.
	Default interface{} `json:"default,omitempty"`

	// The defined text description of the variable.
	Description string `json:"description,omitempty"`

	// Whether the variable is marked as sensitive
	Sensitive bool `json:"sensitive,omitempty"`
}

// ConfigProvisioner describes a provisioner declared in a resource
// configuration.
type ConfigProvisioner struct {
	// The type of the provisioner, ie: "local-exec".
	Type string `json:"type,omitempty"`

	// Any non-special configuration values in the provisioner, indexed by
	// key.
	Expressions map[string]*Expression `json:"expressions,omitempty"`
}

// ModuleCall describes a declared "module" within a configuration.
// It also contains the data for the module itself.
type ModuleCall struct {
	// The contents of the "source" field.
	Source string `json:"source,omitempty"`

	// Any non-special configuration values in the module, indexed by
	// key.
	Expressions map[string]*Expression `json:"expressions,omitempty"`

	// The expression data for the "count" value in the module.
	CountExpression *Expression `json:"count_expression,omitempty"`

	// The expression data for the "for_each" value in the module.
	ForEachExpression *Expression `json:"for_each_expression,omitempty"`

	// The configuration data for the module itself.
	Module *ConfigModule `json:"module,omitempty"`

	// The version constraint for modules that come from the registry.
	VersionConstraint string `json:"version_constraint,omitempty"`

	// The explicit resource dependencies for the "depends_on" value.
	// As it must be a slice of references, Expression is not used.
	DependsOn []string `json:"depends_on,omitempty"`
}
<commit_msg>Add full name field to provider config (#50)<commit_after>package tfjson

import (
	"encoding/json"
	"errors"
)

// Config represents the complete configuration source.
type Config struct {
	// A map of all provider instances across all modules in the
	// configuration.
	//
	// The index for this field is opaque and should not be parsed. Use
	// the individual fields in ProviderConfig to discern actual data
	// about the provider such as name, alias, or defined module.
	ProviderConfigs map[string]*ProviderConfig `json:"provider_config,omitempty"`

	// The root module in the configuration. Any child modules descend
	// off of here.
	RootModule *ConfigModule `json:"root_module,omitempty"`
}

// Validate checks to ensure that the config is present.
func (c *Config) Validate() error {
	if c == nil {
		return errors.New("config is nil")
	}

	return nil
}

func (c *Config) UnmarshalJSON(b []byte) error {
	type rawConfig Config
	var config rawConfig

	err := json.Unmarshal(b, &config)
	if err != nil {
		return err
	}

	*c = *(*Config)(&config)

	return c.Validate()
}

// ProviderConfig describes a provider configuration instance.
type ProviderConfig struct {
	// The name of the provider, ie: "aws".
	Name string `json:"name,omitempty"`

	// The fully-specified name of the provider, ie: "registry.terraform.io/hashicorp/aws".
	FullName string `json:"full_name,omitempty"`

	// The alias of the provider, ie: "us-east-1".
	Alias string `json:"alias,omitempty"`

	// The address of the module the provider is declared in.
	ModuleAddress string `json:"module_address,omitempty"`

	// Any non-special configuration values in the provider, indexed by
	// key.
	Expressions map[string]*Expression `json:"expressions,omitempty"`

	// The defined version constraint for this provider.
	VersionConstraint string `json:"version_constraint,omitempty"`
}

// ConfigModule describes a module in Terraform configuration.
type ConfigModule struct {
	// The outputs defined in the module.
	Outputs map[string]*ConfigOutput `json:"outputs,omitempty"`

	// The resources defined in the module.
	Resources []*ConfigResource `json:"resources,omitempty"`

	// Any "module" stanzas within the specific module.
	ModuleCalls map[string]*ModuleCall `json:"module_calls,omitempty"`

	// The variables defined in the module.
	Variables map[string]*ConfigVariable `json:"variables,omitempty"`
}

// ConfigOutput defines an output as defined in configuration.
type ConfigOutput struct {
	// Indicates whether or not the output was marked as sensitive.
	Sensitive bool `json:"sensitive,omitempty"`

	// The defined value of the output.
	Expression *Expression `json:"expression,omitempty"`

	// The defined description of this output.
	Description string `json:"description,omitempty"`

	// The defined dependencies tied to this output.
	DependsOn []string `json:"depends_on,omitempty"`
}

// ConfigResource is the configuration representation of a resource.
type ConfigResource struct {
	// The address of the resource relative to the module that it is
	// in.
	Address string `json:"address,omitempty"`

	// The resource mode.
	Mode ResourceMode `json:"mode,omitempty"`

	// The type of resource, ie: "null_resource" in
	// "null_resource.foo".
	Type string `json:"type,omitempty"`

	// The name of the resource, ie: "foo" in "null_resource.foo".
	Name string `json:"name,omitempty"`

	// An opaque key representing the provider configuration this
	// module uses. Note that there are more than one circumstance that
	// this key will not match what is found in the ProviderConfigs
	// field in the root Config structure, and as such should not be
	// relied on for that purpose.
	ProviderConfigKey string `json:"provider_config_key,omitempty"`

	// The list of provisioner defined for this configuration. This
	// will be nil if no providers are defined.
	Provisioners []*ConfigProvisioner `json:"provisioners,omitempty"`

	// Any non-special configuration values in the resource, indexed by
	// key.
	Expressions map[string]*Expression `json:"expressions,omitempty"`

	// The resource's configuration schema version. With access to the
	// specific Terraform provider for this resource, this can be used
	// to determine the correct schema for the configuration data
	// supplied in Expressions.
	SchemaVersion uint64 `json:"schema_version"`

	// The expression data for the "count" value in the resource.
	CountExpression *Expression `json:"count_expression,omitempty"`

	// The expression data for the "for_each" value in the resource.
	ForEachExpression *Expression `json:"for_each_expression,omitempty"`

	// The contents of the "depends_on" config directive, which
	// declares explicit dependencies for this resource.
	DependsOn []string `json:"depends_on,omitempty"`
}

// ConfigVariable defines a variable as defined in configuration.
type ConfigVariable struct {
	// The defined default value of the variable.
	Default interface{} `json:"default,omitempty"`

	// The defined text description of the variable.
	Description string `json:"description,omitempty"`

	// Whether the variable is marked as sensitive
	Sensitive bool `json:"sensitive,omitempty"`
}

// ConfigProvisioner describes a provisioner declared in a resource
// configuration.
type ConfigProvisioner struct {
	// The type of the provisioner, ie: "local-exec".
	Type string `json:"type,omitempty"`

	// Any non-special configuration values in the provisioner, indexed by
	// key.
	Expressions map[string]*Expression `json:"expressions,omitempty"`
}

// ModuleCall describes a declared "module" within a configuration.
// It also contains the data for the module itself.
type ModuleCall struct {
	// The contents of the "source" field.
	Source string `json:"source,omitempty"`

	// Any non-special configuration values in the module, indexed by
	// key.
	Expressions map[string]*Expression `json:"expressions,omitempty"`

	// The expression data for the "count" value in the module.
	CountExpression *Expression `json:"count_expression,omitempty"`

	// The expression data for the "for_each" value in the module.
	ForEachExpression *Expression `json:"for_each_expression,omitempty"`

	// The configuration data for the module itself.
	Module *ConfigModule `json:"module,omitempty"`

	// The version constraint for modules that come from the registry.
	VersionConstraint string `json:"version_constraint,omitempty"`

	// The explicit resource dependencies for the "depends_on" value.
	// As it must be a slice of references, Expression is not used.
	DependsOn []string `json:"depends_on,omitempty"`
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/codeskyblue/kexec"
	"github.com/howeyc/fsnotify"
	yaml "gopkg.in/yaml.v2"
)

const FWCONFIG = ".fwc.yml"

var signalMaps = map[string]os.Signal{
	"INT":  syscall.SIGINT,
	"HUP":  syscall.SIGHUP,
	"QUIT": syscall.SIGQUIT,
	"TRAP": syscall.SIGTRAP,
	"TERM": syscall.SIGTERM,
	"KILL": syscall.SIGKILL, // kill -9
}

func init() {
	for key, val := range signalMaps {
		signalMaps["SIG"+key] = val
		signalMaps[fmt.Sprintf("%d", val)] = val
	}
}

type TriggerEvent struct {
	Pattens       []string          `yaml:"pattens"`
	Environ       map[string]string `yaml:"env"`
	Command       string            `yaml:"cmd"`
	delayDuration time.Duration     `yaml:"-"`
	Delay         string            `yaml:"delay"`
	Signal        string            `yaml:"signal"`
	killSignal    os.Signal         `yaml:"-"`
	kcmd          *kexec.KCommand
}

func (this *TriggerEvent) Start() error {
	cmd := kexec.CommandString(this.Command)
	env := os.Environ()
	for key, val := range this.Environ {
		env = append(env, fmt.Sprintf("%s=%s", key, val))
	}
	cmd.Env = env
	this.kcmd = cmd
	return cmd.Start()
}

func (this *TriggerEvent) Stop() {
	if this.kcmd != nil {
		this.kcmd.Terminate(os.Interrupt)
		this.kcmd = nil
	}
}

func (this *TriggerEvent) WatchEvent(evtC chan FSEvent, wg *sync.WaitGroup) {
	this.Start()
	for evt := range evtC {
		log.Println(evt)
		this.Stop()
		log.Printf("delay: %v", this.Delay)
		time.Sleep(this.delayDuration)
		this.Start()
	}
	this.Stop()
	wg.Done()
}

type FSEvent struct {
	Name string
}

type FWConfig struct {
	Description string         `yaml:"desc"`
	Triggers    []TriggerEvent `yaml:"triggers"`
	WatchPaths  []string       `yaml:"watch_paths"`
	WatchDepth  int            `yaml:"watch_depth"`

	// Paths     []string `json:"paths"`
	// Depth     int      `json:"depth"`
	// Exclude   []string `json:"exclude"`
	// reExclude []*regexp.Regexp
	// Include   []string `json:"include"`
	// reInclude []*regexp.Regexp
	// bufdur    time.Duration `json:"-"`
	// Command   interface{}   `json:"command"` // can be string or []string
	// cmd       []string
	// Env       map[string]string `json:"env"`

	// AutoRestart     bool          `json:"autorestart"`
	// RestartInterval time.Duration `json:"restart-interval"`
	// KillSignal      string        `json:"kill-signal"`

	// w       *fsnotify.Watcher
	// modtime map[string]time.Time
	// sig     chan string
	// sigOS   chan os.Signal
}

func fixFWConfig(in FWConfig) (out FWConfig, err error) {
	out = in
	for idx, trigger := range in.Triggers {
		outTg := &out.Triggers[idx]
		if trigger.Delay == "" {
			outTg.Delay = "100ms"
		}
		outTg.delayDuration, err = time.ParseDuration(outTg.Delay)
		if err != nil {
			return
		}
		if outTg.Signal == "" {
			outTg.Signal = "HUP"
		}
		outTg.killSignal = signalMaps[outTg.Signal]
	}
	if len(out.WatchPaths) == 0 {
		out.WatchPaths = append(out.WatchPaths, ".")
	}
	if out.WatchDepth == 0 {
		out.WatchDepth = 5
	}
	return
}

func readString(prompt, value string) string {
	fmt.Printf("[?] %s (%s) ", prompt, value)
	var s = value
	fmt.Scanf("%s", &s)
	return s
}

func genFWConfig() FWConfig {
	var (
		name    string
		command string
	)
	cwd, _ := os.Getwd()
	name = filepath.Base(cwd)
	name = readString("name:", name)

	for command == "" {
		command = readString("command:", "go test -v")
	}
	fwc := FWConfig{
		Description: fmt.Sprintf("Auto generated by fswatch [%s]", name),
		Triggers: []TriggerEvent{{
			Pattens: []string{"*.go", "*.c", "*.py"},
			Environ: map[string]string{
				"DEBUG": "1",
			},
			Command: command,
		}},
	}
	out, _ := fixFWConfig(fwc)
	return out
}

func ListAllDir(path string, depth int) (dirs []string, err error) {
	baseNumSeps := strings.Count(path, string(os.PathSeparator))
	err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if info.IsDir() {
			base := info.Name()
			if base != "." && strings.HasPrefix(base, ".") { // ignore hidden dir
				return filepath.SkipDir
			}

			pathDepth := strings.Count(path, string(os.PathSeparator)) - baseNumSeps
			if pathDepth > depth {
				return filepath.SkipDir
			}
			dirs = append(dirs, path)

			fmt.Println(">>> watch dir: ", path)
		}
		return nil
	})
	return
}

func IsDirectory(path string) bool {
	pinfo, err := os.Stat(path)
	return err == nil && pinfo.IsDir()
}

var fileModifyTimeMap = make(map[string]time.Time)

func IsChanged(path string) bool {
	pinfo, err := os.Stat(path)
	if err != nil {
		return true
	}
	mtime := pinfo.ModTime()
	if mtime.Sub(fileModifyTimeMap[path]) > time.Millisecond*100 { // 100ms
		fileModifyTimeMap[path] = mtime
		return true
	}
	return false
}

func main() {
	dirs, err := ListAllDir(".", 3)
	if err != nil {
		log.Fatal(err)
	}
	fsw, _ := fsnotify.NewWatcher()
	for _, dir := range dirs {
		fsw.Watch(dir)
	}
	for evt := range fsw.Event {
		log.Println(evt)
		if evt.IsCreate() && IsDirectory(evt.Name) {
			log.Println("Add watcher")
			fsw.Watch(evt.Name)
			continue
		}
		if evt.IsDelete() {
			log.Println("Remove watcher")
			fsw.RemoveWatch(evt.Name)
		}
		if IsChanged(evt.Name) {
			log.Printf("IsChanged: %s", evt.Name)
		}
	}
	return

	flag.Parse()

	if flag.NArg() == 0 {
		data, err := ioutil.ReadFile(FWCONFIG)
		if err != nil {
			log.Fatal(err)
		}
		fwc := FWConfig{}
		if err = yaml.Unmarshal(data, &fwc); err != nil {
			log.Fatal(err)
		}
		log.Println(string(data))

		// fsw := fsnotify.NewWatcher()
		// fsw.Watch(path)

		evtC := make(chan FSEvent, 1)
		wg := &sync.WaitGroup{}
		for _, tg := range fwc.Triggers {
			wg.Add(1)
			go tg.WatchEvent(evtC, wg)
		}
		evtC <- FSEvent{
			Name: "hello.go",
		}
		time.Sleep(time.Millisecond * 400)
		close(evtC)
		wg.Wait()
		return
	}

	subcmd := flag.Arg(0)

	switch subcmd {
	case "init":
		log.Println("Initial")
		fwc := genFWConfig()
		data, err := yaml.Marshal(fwc)
		if err != nil {
			log.Fatal(err)
		}
		yn := readString(fmt.Sprintf("Save to file (%s)", FWCONFIG), "Y")
		if strings.ToLower(yn) == "y" {
			ioutil.WriteFile(FWCONFIG, data, 0644)
			fmt.Println("Saved!")
		} else {
			fmt.Println(string(data))
		}
	case "start":
		fallthrough
	default:
		log.Println("Unknown:", subcmd)
	}

}
<commit_msg>add dockerignore<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"

	ignore "github.com/codeskyblue/dockerignore"
	"github.com/codeskyblue/kexec"
	"github.com/howeyc/fsnotify"
	yaml "gopkg.in/yaml.v2"
)

const FWCONFIG = ".fwc.yml"

var signalMaps = map[string]os.Signal{
	"INT":  syscall.SIGINT,
	"HUP":  syscall.SIGHUP,
	"QUIT": syscall.SIGQUIT,
	"TRAP": syscall.SIGTRAP,
	"TERM": syscall.SIGTERM,
	"KILL": syscall.SIGKILL, // kill -9
}

func init() {
	for key, val := range signalMaps {
		signalMaps["SIG"+key] = val
		signalMaps[fmt.Sprintf("%d", val)] = val
	}
}

type TriggerEvent struct {
	Pattens       []string          `yaml:"pattens"`
	Environ       map[string]string `yaml:"env"`
	Command       string            `yaml:"cmd"`
	delayDuration time.Duration     `yaml:"-"`
	Delay         string            `yaml:"delay"`
	Signal        string            `yaml:"signal"`
	killSignal    os.Signal         `yaml:"-"`
	kcmd          *kexec.KCommand
}

func (this *TriggerEvent) Start() error {
	cmd := kexec.CommandString(this.Command)
	env := os.Environ()
	for key, val := range this.Environ {
		env = append(env, fmt.Sprintf("%s=%s", key, val))
	}
	cmd.Env = env
	this.kcmd = cmd
	return cmd.Start()
}

func (this *TriggerEvent) Stop() {
	if this.kcmd != nil {
		this.kcmd.Terminate(os.Interrupt)
		this.kcmd = nil
	}
}

func (this *TriggerEvent) WatchEvent(evtC chan FSEvent, wg *sync.WaitGroup) {
	this.Start()
	for evt := range evtC {
		log.Println(evt)
		this.Stop()
		log.Printf("delay: %v", this.Delay)
		time.Sleep(this.delayDuration)
		this.Start()
	}
	this.Stop()
	wg.Done()
}

type FSEvent struct {
	Name string
}

type FWConfig struct {
	Description string         `yaml:"desc"`
	Triggers    []TriggerEvent `yaml:"triggers"`
	WatchPaths  []string       `yaml:"watch_paths"`
	WatchDepth  int            `yaml:"watch_depth"`

	// Paths     []string `json:"paths"`
	// Depth     int      `json:"depth"`
	// Exclude   []string `json:"exclude"`
	// reExclude []*regexp.Regexp
	// Include   []string `json:"include"`
	// reInclude []*regexp.Regexp
	// bufdur    time.Duration `json:"-"`
	// Command   interface{}   `json:"command"` // can be string or []string
	// cmd       []string
	// Env       map[string]string `json:"env"`

	// AutoRestart     bool          `json:"autorestart"`
	// RestartInterval time.Duration `json:"restart-interval"`
	// KillSignal      string        `json:"kill-signal"`

	// w       *fsnotify.Watcher
	// modtime map[string]time.Time
	// sig     chan string
	// sigOS   chan os.Signal
}

func fixFWConfig(in FWConfig) (out FWConfig, err error) {
	out = in
	for idx, trigger := range in.Triggers {
		outTg := &out.Triggers[idx]
		if trigger.Delay == "" {
			outTg.Delay = "100ms"
		}
		outTg.delayDuration, err = time.ParseDuration(outTg.Delay)
		if err != nil {
			return
		}
		if outTg.Signal == "" {
			outTg.Signal = "HUP"
		}
		outTg.killSignal = signalMaps[outTg.Signal]
	}
	if len(out.WatchPaths) == 0 {
		out.WatchPaths = append(out.WatchPaths, ".")
	}
	if out.WatchDepth == 0 {
		out.WatchDepth = 5
	}
	return
}

func readString(prompt, value string) string {
	fmt.Printf("[?] %s (%s) ", prompt, value)
	var s = value
	fmt.Scanf("%s", &s)
	return s
}

func genFWConfig() FWConfig {
	var (
		name    string
		command string
	)
	cwd, _ := os.Getwd()
	name = filepath.Base(cwd)
	name = readString("name:", name)

	for command == "" {
		command = readString("command:", "go test -v")
	}
	fwc := FWConfig{
		Description: fmt.Sprintf("Auto generated by fswatch [%s]", name),
		Triggers: []TriggerEvent{{
			Pattens: []string{"*.go", "*.c", "*.py"},
			Environ: map[string]string{
				"DEBUG": "1",
			},
			Command: command,
		}},
	}
	out, _ := fixFWConfig(fwc)
	return out
}

func ListAllDir(path string, depth int) (dirs []string, err error) {
	baseNumSeps := strings.Count(path, string(os.PathSeparator))
	err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if info.IsDir() {
			base := info.Name()
			if base != "." && strings.HasPrefix(base, ".") { // ignore hidden dir
				return filepath.SkipDir
			}

			pathDepth := strings.Count(path, string(os.PathSeparator)) - baseNumSeps
			if pathDepth > depth {
				return filepath.SkipDir
			}
			dirs = append(dirs, path)

			fmt.Println(">>> watch dir: ", path)
		}
		return nil
	})
	return
}

func IsDirectory(path string) bool {
	pinfo, err := os.Stat(path)
	return err == nil && pinfo.IsDir()
}

var fileModifyTimeMap = make(map[string]time.Time)

func IsChanged(path string) bool {
	pinfo, err := os.Stat(path)
	if err != nil {
		return true
	}
	mtime := pinfo.ModTime()
	if mtime.Sub(fileModifyTimeMap[path]) > time.Millisecond*100 { // 100ms
		fileModifyTimeMap[path] = mtime
		return true
	}
	return false
}

func main() {
	dirs, err := ListAllDir(".", 3)
	if err != nil {
		log.Fatal(err)
	}
	fsw, _ := fsnotify.NewWatcher()
	for _, dir := range dirs {
		fsw.Watch(dir)
	}
	for evt := range fsw.Event {
		log.Println(evt)
		if evt.IsCreate() && IsDirectory(evt.Name) {
			log.Println("Add watcher")
			fsw.Watch(evt.Name)
			continue
		}
		if evt.IsDelete() {
			log.Println("Remove watcher")
			fsw.RemoveWatch(evt.Name)
		}
		if IsChanged(evt.Name) {
			log.Printf("IsChanged: %s", evt.Name)
		}

		rd := ioutil.NopCloser(bytes.NewBufferString("*.exe"))
		patterns, err := ignore.ReadIgnore(rd)
		if err != nil {
			log.Fatal(err)
		}

		log.Println(patterns)
	}
	return

	flag.Parse()

	if flag.NArg() == 0 {
		data, err := ioutil.ReadFile(FWCONFIG)
		if err != nil {
			log.Fatal(err)
		}
		fwc := FWConfig{}
		if err = yaml.Unmarshal(data, &fwc); err != nil {
			log.Fatal(err)
		}
		log.Println(string(data))

		// fsw := fsnotify.NewWatcher()
		// fsw.Watch(path)

		evtC := make(chan FSEvent, 1)
		wg := &sync.WaitGroup{}
		for _, tg := range fwc.Triggers {
			wg.Add(1)
			go tg.WatchEvent(evtC, wg)
		}
		evtC <- FSEvent{
			Name: "hello.go",
		}
		time.Sleep(time.Millisecond * 400)
		close(evtC)
		wg.Wait()
		return
	}

	subcmd := flag.Arg(0)

	switch subcmd {
	case "init":
		log.Println("Initial")
		fwc := genFWConfig()
		data, err := yaml.Marshal(fwc)
		if err != nil {
			log.Fatal(err)
		}
		yn := readString(fmt.Sprintf("Save to file (%s)", FWCONFIG), "Y")
		if strings.ToLower(yn) == "y" {
			ioutil.WriteFile(FWCONFIG, data, 0644)
			fmt.Println("Saved!")
		} else {
			fmt.Println(string(data))
		}
	case "start":
		fallthrough
	default:
		log.Println("Unknown:", subcmd)
	}

}
<|endoftext|>


<commit_before>/*
Copyright (c) 2016, Hasani Hunter
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/miekg/dns"
	"io/ioutil"
	"os"
	"path"
)

type DNSConfig struct {
	Host       string
	Port       int64
	Forwarders []DNSForwardingServer
	Filters    []DNSFilter
	Logfile    string
}

func setupDefaultDNSForwardingServers() []DNSForwardingServer {

	// use free default dns servers
	hurricaneElectric1 := DNSForwardingServer{"74.82.42.42", 53, "udp"}
	opennic1 := DNSForwardingServer{"107.150.40.234", 53, "udp"}
	opennic2 := DNSForwardingServer{"162.211.64.20", 53, "udp"}
	opennic3 := DNSForwardingServer{"50.116.23.211", 53, "udp"}
	opennic4 := DNSForwardingServer{"50.116.40.226", 53, "udp"}
	freedns1 := DNSForwardingServer{"37.235.1.174", 53, "udp"}
	freedns2 := DNSForwardingServer{"37.235.1.177", 53, "udp"}
	google1 := DNSForwardingServer{"8.8.8.8", 53, "udp"}
	google2 := DNSForwardingServer{"8.8.4.4", 53, "udp"}

	dnsServers := []DNSForwardingServer{
		hurricaneElectric1,
		opennic1,
		opennic2,
		opennic3,
		opennic4,
		freedns1,
		freedns2,
		google1,
		google2,
	}

	return dnsServers
}

func parseConfigFile(configPath string) (*DNSConfig, error) {

	var configMap map[string]interface{}

	// try to read the config file
	bytes, configError := ioutil.ReadFile(configPath)

	if configError != nil {
		return nil, configError
	}

	err := json.Unmarshal(bytes, &configMap)

	if err != nil {
		return nil, err
	}

	config := &DNSConfig{}

	host, hostExists := configMap["host"].(string)

	if !hostExists {
		// bind to localhost interface by default
		host = "localhost"
	}

	config.Host = host

	port, portExists := configMap["port"].(int64)

	if !portExists {
		// bind to port 1234 by default
		port = 1234
	}

	config.Port = port

	forwardingSlice, forwardingMapExists := configMap["forwarders"].([]interface{})

	dnsServers := []DNSForwardingServer{}

	if !forwardingMapExists {

		// use default forwarding servers
		dnsServers = setupDefaultDNSForwardingServers()

	} else {
		for _, forwardInterface := range forwardingSlice {

			forwardMap := forwardInterface.(map[string]interface{})

			// validate that the host in the forwardMap is legit
			forwardHost, forwardHostExists := forwardMap["host"].(string)

			if !forwardHostExists {
				continue
			}

			// if the forward host is smaller than the smallest ipv4 address.. kick it
			if len(forwardHost) < len("8.8.8.8") {
				continue
			}

			forwardPort, forwardPortExists := forwardMap["port"].(int64)

			if !forwardPortExists {
				// we will just default to port 53
				forwardPort = 53
			}

			forwardProtocol, forwardProtocolExists := forwardMap["protocol"].(string)

			if !forwardProtocolExists {
				// default to udp
				forwardProtocol = "udp"
			} else {
				// make sure that the protocol is either udp or tcp
				if forwardProtocol != "udp" && forwardProtocol != "tcp" {
					// we have a misconfiguration so let the user know
					errorMessage := fmt.Sprintf("%s is an invalid protocol.  Protocol for host: %s must be either udp or tcp",
						forwardProtocol, forwardHost)

					return nil, errors.New(errorMessage)
				}

				// if we get here, then all is good
			}

			dnsServer := DNSForwardingServer{
				forwardHost,
				forwardPort,
				forwardProtocol,
			}

			dnsServers = append(dnsServers, dnsServer)

		}

		if len(dnsServers) < 1 {
			// if we make it all the way down here and we don't have any servers (due to a bad config, then use defaults)
			// use default forwarding servers
			dnsServers = setupDefaultDNSForwardingServers()
		}
	}

	config.Forwarders = dnsServers

	// process any filters
	dnsFilters := []DNSFilter{}

	filterSlice, filtersExists := configMap["filters"].([]interface{})

	if filtersExists {

		for _, filterInterface := range filterSlice {

			filterMap := filterInterface.(map[string]interface{})

			filterHost, filterHostExists := filterMap["host"].(string)

			if !filterHostExists {
				continue
			}

			// if the filter host is smaller than the smallest domain/host address.. keep on rolling
			if len(filterHost) < len("a.io") {
				continue
			}

			filterType, filterTypeExists := filterMap["type"].(string)

			// filter ALL records
			filterInt := dns.TypeANY

			if !filterTypeExists {
				filterType = "ALL"
			}

			if filterType != "ALL" {
				if filterType == "AAAA" {
					filterInt = dns.TypeAAAA
				} else if filterType == "A" {
					filterInt = dns.TypeA
				} else if filterType == "MX" {
					filterInt = dns.TypeMX
				} else if filterType == "TXT" {
					filterInt = dns.TypeTXT
				} else if filterType == "CNAME" {
					filterInt = dns.TypeCNAME
				}
			}

			var exactHostMatching bool

			// ignore matching criteria if the filterType is set to ALL
			if filterType == "ALL" {
				exactHostMatching = false
			} else {

				matchingType, matchingTypeExists := filterMap["matching"].(string)

				if matchingTypeExists {
					if matchingType == "contains" {
						exactHostMatching = false
					} else if matchingType == "exact" {
						exactHostMatching = true
					} else {
						errorMessage := fmt.Sprintf("%s is an invalid matching type.  Filter matching for host: %s must be either \"contains\" or \"exact\"",
							matchingType, filterHost)
						return nil, errors.New(errorMessage)
					}
				} else {
					errorMessage := fmt.Sprintf("Filter is required matching for host: %s must be either \"contains\" or \"exact\"",
						filterHost)
					return nil, errors.New(errorMessage)
				}

			}

			// if we get here, then we have validated the filter
			dnsFilter := DNSFilter{
				filterHost,
				filterInt,
				exactHostMatching,
			}

			dnsFilters = append(dnsFilters, dnsFilter)
		}

	}

	config.Filters = dnsFilters

	logFilePath, logFilePathExists := configMap["logfile"].(string)

	if !logFilePathExists {
		// default to filter-dns.log in the current working directory
		currentDirectory, dirErr := os.Getwd()

		if dirErr != nil {
			return nil, dirErr
		}

		logFilePath = path.Join(currentDirectory, "filter-dns.log")
	}

	setupLogging(logFilePath)

	return config, nil

}
<commit_msg>Fix a bug when reading port numbers.<commit_after>/*
Copyright (c) 2016, Hasani Hunter
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/miekg/dns"
	"io/ioutil"
	"os"
	"path"
)

type DNSConfig struct {
	Host       string
	Port       int64
	Forwarders []DNSForwardingServer
	Filters    []DNSFilter
	Logfile    string
}

func setupDefaultDNSForwardingServers() []DNSForwardingServer {

	// use free default dns servers
	hurricaneElectric1 := DNSForwardingServer{"74.82.42.42", 53, "udp"}
	opennic1 := DNSForwardingServer{"107.150.40.234", 53, "udp"}
	opennic2 := DNSForwardingServer{"162.211.64.20", 53, "udp"}
	opennic3 := DNSForwardingServer{"50.116.23.211", 53, "udp"}
	opennic4 := DNSForwardingServer{"50.116.40.226", 53, "udp"}
	freedns1 := DNSForwardingServer{"37.235.1.174", 53, "udp"}
	freedns2 := DNSForwardingServer{"37.235.1.177", 53, "udp"}
	google1 := DNSForwardingServer{"8.8.8.8", 53, "udp"}
	google2 := DNSForwardingServer{"8.8.4.4", 53, "udp"}

	dnsServers := []DNSForwardingServer{
		hurricaneElectric1,
		opennic1,
		opennic2,
		opennic3,
		opennic4,
		freedns1,
		freedns2,
		google1,
		google2,
	}

	return dnsServers
}

func parseConfigFile(configPath string) (*DNSConfig, error) {

	var configMap map[string]interface{}

	// try to read the config file
	bytes, configError := ioutil.ReadFile(configPath)

	if configError != nil {
		return nil, configError
	}

	err := json.Unmarshal(bytes, &configMap)

	if err != nil {
		return nil, err
	}

	config := &DNSConfig{}

	host, hostExists := configMap["host"].(string)

	if !hostExists {
		// bind to localhost interface by default
		host = "localhost"
	}

	config.Host = host

	port, portExists := configMap["port"].(float64)

	if !portExists {
		// bind to port 1234 by default
		port = 1234
	}

	config.Port = int64(port)

	forwardingSlice, forwardingMapExists := configMap["forwarders"].([]interface{})

	dnsServers := []DNSForwardingServer{}

	if !forwardingMapExists {

		// use default forwarding servers
		dnsServers = setupDefaultDNSForwardingServers()

	} else {
		for _, forwardInterface := range forwardingSlice {

			forwardMap := forwardInterface.(map[string]interface{})

			// validate that the host in the forwardMap is legit
			forwardHost, forwardHostExists := forwardMap["host"].(string)

			if !forwardHostExists {
				continue
			}

			// if the forward host is smaller than the smallest ipv4 address.. kick it
			if len(forwardHost) < len("8.8.8.8") {
				continue
			}

			forwardPort, forwardPortExists := forwardMap["port"].(int64)

			if !forwardPortExists {
				// we will just default to port 53
				forwardPort = 53
			}

			forwardProtocol, forwardProtocolExists := forwardMap["protocol"].(string)

			if !forwardProtocolExists {
				// default to udp
				forwardProtocol = "udp"
			} else {
				// make sure that the protocol is either udp or tcp
				if forwardProtocol != "udp" && forwardProtocol != "tcp" {
					// we have a misconfiguration so let the user know
					errorMessage := fmt.Sprintf("%s is an invalid protocol.  Protocol for host: %s must be either udp or tcp",
						forwardProtocol, forwardHost)

					return nil, errors.New(errorMessage)
				}

				// if we get here, then all is good
			}

			dnsServer := DNSForwardingServer{
				forwardHost,
				forwardPort,
				forwardProtocol,
			}

			dnsServers = append(dnsServers, dnsServer)

		}

		if len(dnsServers) < 1 {
			// if we make it all the way down here and we don't have any servers (due to a bad config, then use defaults)
			// use default forwarding servers
			dnsServers = setupDefaultDNSForwardingServers()
		}
	}

	config.Forwarders = dnsServers

	// process any filters
	dnsFilters := []DNSFilter{}

	filterSlice, filtersExists := configMap["filters"].([]interface{})

	if filtersExists {

		for _, filterInterface := range filterSlice {

			filterMap := filterInterface.(map[string]interface{})

			filterHost, filterHostExists := filterMap["host"].(string)

			if !filterHostExists {
				continue
			}

			// if the filter host is smaller than the smallest domain/host address.. keep on rolling
			if len(filterHost) < len("a.io") {
				continue
			}

			filterType, filterTypeExists := filterMap["type"].(string)

			// filter ALL records
			filterInt := dns.TypeANY

			if !filterTypeExists {
				filterType = "ALL"
			}

			if filterType != "ALL" {
				if filterType == "AAAA" {
					filterInt = dns.TypeAAAA
				} else if filterType == "A" {
					filterInt = dns.TypeA
				} else if filterType == "MX" {
					filterInt = dns.TypeMX
				} else if filterType == "TXT" {
					filterInt = dns.TypeTXT
				} else if filterType == "CNAME" {
					filterInt = dns.TypeCNAME
				}
			}

			var exactHostMatching bool

			// ignore matching criteria if the filterType is set to ALL
			if filterType == "ALL" {
				exactHostMatching = false
			} else {

				matchingType, matchingTypeExists := filterMap["matching"].(string)

				if matchingTypeExists {
					if matchingType == "contains" {
						exactHostMatching = false
					} else if matchingType == "exact" {
						exactHostMatching = true
					} else {
						errorMessage := fmt.Sprintf("%s is an invalid matching type.  Filter matching for host: %s must be either \"contains\" or \"exact\"",
							matchingType, filterHost)
						return nil, errors.New(errorMessage)
					}
				} else {
					errorMessage := fmt.Sprintf("Filter is required matching for host: %s must be either \"contains\" or \"exact\"",
						filterHost)
					return nil, errors.New(errorMessage)
				}

			}

			// if we get here, then we have validated the filter
			dnsFilter := DNSFilter{
				filterHost,
				filterInt,
				exactHostMatching,
			}

			dnsFilters = append(dnsFilters, dnsFilter)
		}

	}

	config.Filters = dnsFilters

	logFilePath, logFilePathExists := configMap["logfile"].(string)

	if !logFilePathExists {
		// default to filter-dns.log in the current working directory
		currentDirectory, dirErr := os.Getwd()

		if dirErr != nil {
			return nil, dirErr
		}

		logFilePath = path.Join(currentDirectory, "filter-dns.log")
	}

	setupLogging(logFilePath)

	return config, nil

}
<|endoftext|>


<commit_before>package main

import "fmt"
import "github.com/aws/aws-sdk-go/service/iam"

/*

# lambda-phage config file sample

name: my-first-lambda-function
description: provides some sample stuff
pkg:
  name: my-first-lambda-function.zip
deploy:
  type: s3
  s3-bucket: test-bucket
  use-versioning: true
*/

type Config struct {
	Name        *string
	Description *string
	Archive     *string
	EntryPoint  *string
	MemorySize  *uint
	Runtime     *string
	Timeout     *uint
	IamRole     struct {
		Arn  *string
		Name *string
	}
	Location *struct {
		S3Bucket        *string
		S3Key           *string
		S3ObjectVersion *string
	}
}

// returns the arn for the role specified
func (c Config) getRoleArn() (*string, error) {
	// if the config file has an ARN listed,
	// that takes precedence
	if c.IamRole.Arn != "" {
		return c.IamRole.Arn, nil
	} else if c.IamRole.Name != "" {
		// look up the iam role name
		iamRole, err := getIamPolicy(c.IamRole.Name)
		if err != nil {
			return nil, err
		}
		return iamRole, err
	} else {
		// TODO: create a default standard role
		// and update config file
	}

	return nil, fmt
}

// gets an IAM policy by name
func getIamPolicy(name string) (*string, error) {
	i := iam.New(nil)

	r, err := i.GetRole(&iam.GetRoleInput{
		RoleName: &name,
	})

	if err != nil {
		return aws.String(""), err
	}

	return r.Role.ARN, nil
}
<commit_msg>change config to pointer receiver add method for getting s3 bucket info<commit_after>package main

import "fmt"
import "github.com/aws/aws-sdk-go/service/iam"
import "github.com/aws/aws-sdk-go/aws"
import "github.com/tj/go-debug"
import "strings"

/*

# lambda-phage config file sample

name: my-first-lambda-function
description: provides some sample stuff
pkg:
  name: my-first-lambda-function.zip
deploy:
  type: s3
  s3-bucket: test-bucket
  use-versioning: true
*/

type Config struct {
	Name        *string
	Description *string
	Archive     *string
	EntryPoint  *string
	MemorySize  *uint
	Runtime     *string
	Timeout     *uint
	IamRole     struct {
		Arn  *string
		Name *string
	}
	Location *struct {
		S3Bucket        *string
		S3Key           *string
		S3ObjectVersion *string
	}
}

// returns the arn for the role specified
func (c *Config) getRoleArn() (*string, error) {
	if c.IamRole.Arn == nil &&
		c.IamRole.Name == nil {
		return nil, fmt.Errorf("Missing ARN config!")
	}

	// if the config file has an ARN listed,
	// that takes precedence
	if *c.IamRole.Arn != "" {
		return c.IamRole.Arn, nil
	} else if *c.IamRole.Name != "" {
		// look up the iam role name
		iamRole, err := getIamPolicy(*c.IamRole.Name)
		if err != nil {
			return nil, err
		}
		return iamRole, err
	} else {
		// TODO: create a default standard role
		// and update config file
	}

	return nil, fmt.Errorf("how did you get here")
}

// returns a normalized S3 path to a file
// based on config information
//
// requires the file name of the archive you'll upload
//
// returns the bucket and the key
func (c *Config) getS3Info(fName string) (bucket, key *string) {
	debug := debug.Debug("config.getS3Info")
	loc := c.Location
	if loc == nil {
		debug("no upload location info found")
		return nil, nil
	}

	if loc.S3Bucket == nil {
		// TODO: make these return an error instead??
		debug("upload location info found, but s3 bucket missing")
		return nil, nil
	}

	b := *loc.S3Bucket
	var k string
	if loc.S3Key == nil {
		debug("no s3 key in location config, using file name")
		// no key in config?
		// then the key is the name of the file
		// being passed in
		k = fName
	} else if loc.S3Key != nil &&
		len(*loc.S3Key) > 0 {
		// key in config? let's see
		// if it looks like a zip file
		if strings.Index(*loc.S3Key, ".zip") > -1 {
			// great, we can use this one for the key
			k = *loc.S3Key
		} else {
			// if there's no .zip in the s3Key config
			// setting, then assume this is to
			// be the first part in a directory
			dir := *loc.S3Key
			sl := []byte("/")
			if dir[len(dir)-1] != sl[0] {
				dir += "/"
			}

			k = dir + fName
		}
	} else {
		debug("empty s3key found in config file")
		k = fName
	}

	return &b, &k
}

// gets an IAM policy by name
func getIamPolicy(name string) (*string, error) {
	i := iam.New(nil)

	r, err := i.GetRole(&iam.GetRoleInput{
		RoleName: &name,
	})

	if err != nil {
		return aws.String(""), err
	}

	return r.Role.ARN, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"syscall"

	"gnd.la/config"
	"gnd.la/log"
	"gnd.la/util/stringutil"
)

type Config struct {
	File             string
	Command          string
	Name             string
	Dir              string
	Env              map[string]string
	Start            bool `default:"true"`
	User             string
	Group            string
	Priority         int `default:"1000"`
	Watchdog         *Watchdog
	WatchdogInterval int `default:"300"`
	MaxOpenFiles     int
	Log              *Logger
	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 := stringutil.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.Dir(fields[0])
	}
	cmd := &exec.Cmd{Path: fields[0], Args: fields, Dir: dir}
	for k, v := range c.Env {
		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
	}
	if _, ok := c.Env["GOMAXPROCS"]; !ok {
		cmd.Env = append(cmd.Env, fmt.Sprintf("GOMAXPROCS=%d", runtime.NumCPU()))
	}
	for _, v := range os.Environ() {
		if p := strings.IndexByte(v, '='); p >= 0 {
			k := v[:p]
			if _, ok := c.Env[k]; !ok {
				cmd.Env = append(cmd.Env, v)
			}
		}
	}
	info, err := os.Stat(fields[0])
	if err != nil {
		return nil, err
	}
	stat := info.Sys().(*syscall.Stat_t)
	uid := stat.Uid
	gid := stat.Gid
	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)
		}
	}
	var cred *syscall.Credential
	if uid != 0 || gid != 0 {
		cred = &syscall.Credential{Uid: uid, Gid: gid}
	}
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Credential: cred,
		Pdeathsig:  syscall.SIGQUIT, // Send SIGQUIT to children if parent exits
	}
	log.Debugf("%s wd: %s, env: %s, cred: %+v", c.ServiceName(), dir, cmd.Env, cred)
	return cmd, nil
}

func (c *Config) ServiceName() string {
	if c.Name != "" {
		return c.Name
	}
	return c.File
}

func (g *Governator) servicesDir() string {
	return filepath.Join(g.configDir, "services")
}

func (g *Governator) servicePath(filename string) string {
	return filepath.Join(g.servicesDir(), filename)
}

func (g *Governator) configDirIsDefault() bool {
	return g.configDir == defaultConfigDir
}

func (g *Governator) parseConfig(filename string) *Config {
	cfg := &Config{File: filename}
	err := config.ParseFile(g.servicePath(filename), cfg)
	cfg.Err = err
	if cfg.Log == nil {
		cfg.Log = new(Logger)
		cfg.Log.Parse("")
	}
	cfg.Log.Name = cfg.ServiceName()
	return cfg
}

func (g *Governator) parseConfigs() ([]*Config, error) {
	dir := g.servicesDir()
	if g.configDirIsDefault() {
		if err := os.MkdirAll(dir, 0755); err != nil {
			return nil, fmt.Errorf("error creating services directory %s: %s", dir, err)
		}
	}
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, fmt.Errorf("error reading config directory %s: %s", dir, err)
	}
	var configs []*Config
	for _, v := range files {
		name := v.Name()
		if g.shouldIgnoreFile(name, false) {
			continue
		}
		cfg := g.parseConfig(name)
		log.Debugf("Parsed config %s: %+v", name, cfg)
		configs = append(configs, cfg)
	}
	return configs, nil
}

func (g *Governator) shouldIgnoreFile(name string, deleted bool) bool {
	if name == "" || name[0] == '.' || strings.HasSuffix(name, "~") {
		return true
	}
	if !deleted {
		info, err := os.Stat(g.servicePath(name))
		if err != nil || info.Size() == 0 || info.IsDir() {
			return true
		}
	}
	return false
}
<commit_msg>Correctly initialize Config Name field<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"syscall"

	"gnd.la/config"
	"gnd.la/log"
	"gnd.la/util/stringutil"
)

type Config struct {
	File             string
	Command          string
	Name             string
	Dir              string
	Env              map[string]string
	Start            bool `default:"true"`
	User             string
	Group            string
	Priority         int `default:"1000"`
	Watchdog         *Watchdog
	WatchdogInterval int `default:"300"`
	MaxOpenFiles     int
	Log              *Logger
	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 := stringutil.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.Dir(fields[0])
	}
	cmd := &exec.Cmd{Path: fields[0], Args: fields, Dir: dir}
	for k, v := range c.Env {
		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
	}
	if _, ok := c.Env["GOMAXPROCS"]; !ok {
		cmd.Env = append(cmd.Env, fmt.Sprintf("GOMAXPROCS=%d", runtime.NumCPU()))
	}
	for _, v := range os.Environ() {
		if p := strings.IndexByte(v, '='); p >= 0 {
			k := v[:p]
			if _, ok := c.Env[k]; !ok {
				cmd.Env = append(cmd.Env, v)
			}
		}
	}
	info, err := os.Stat(fields[0])
	if err != nil {
		return nil, err
	}
	stat := info.Sys().(*syscall.Stat_t)
	uid := stat.Uid
	gid := stat.Gid
	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)
		}
	}
	var cred *syscall.Credential
	if uid != 0 || gid != 0 {
		cred = &syscall.Credential{Uid: uid, Gid: gid}
	}
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Credential: cred,
		Pdeathsig:  syscall.SIGQUIT, // Send SIGQUIT to children if parent exits
	}
	log.Debugf("%s wd: %s, env: %s, cred: %+v", c.ServiceName(), dir, cmd.Env, cred)
	return cmd, nil
}

func (c *Config) ServiceName() string {
	if c.Name != "" {
		return c.Name
	}
	return c.File
}

func (g *Governator) servicesDir() string {
	return filepath.Join(g.configDir, "services")
}

func (g *Governator) servicePath(filename string) string {
	return filepath.Join(g.servicesDir(), filename)
}

func (g *Governator) configDirIsDefault() bool {
	return g.configDir == defaultConfigDir
}

func (g *Governator) parseConfig(filename string) *Config {
	cfg := &Config{File: filename}
	err := config.ParseFile(g.servicePath(filename), cfg)
	cfg.Err = err
	if cfg.Log == nil {
		cfg.Log = new(Logger)
		cfg.Log.Parse("")
	}
	cfg.Name = cfg.ServiceName()
	cfg.Log.Name = cfg.Name
	return cfg
}

func (g *Governator) parseConfigs() ([]*Config, error) {
	dir := g.servicesDir()
	if g.configDirIsDefault() {
		if err := os.MkdirAll(dir, 0755); err != nil {
			return nil, fmt.Errorf("error creating services directory %s: %s", dir, err)
		}
	}
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, fmt.Errorf("error reading config directory %s: %s", dir, err)
	}
	var configs []*Config
	for _, v := range files {
		name := v.Name()
		if g.shouldIgnoreFile(name, false) {
			continue
		}
		cfg := g.parseConfig(name)
		log.Debugf("Parsed config %s: %+v", name, cfg)
		configs = append(configs, cfg)
	}
	return configs, nil
}

func (g *Governator) shouldIgnoreFile(name string, deleted bool) bool {
	if name == "" || name[0] == '.' || strings.HasSuffix(name, "~") {
		return true
	}
	if !deleted {
		info, err := os.Stat(g.servicePath(name))
		if err != nil || info.Size() == 0 || info.IsDir() {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package provider

import (
	"errors"
	"fmt"
	"net/url"
	"path/filepath"
	"time"

	log "github.com/Sirupsen/logrus"
	logx "github.com/mistifyio/mistify-logrus-ext"
	"github.com/mitchellh/mapstructure"
	flag "github.com/spf13/pflag"
	"github.com/spf13/viper"
)

// Config holds all configuration for the provider.
type Config struct {
	viper   *viper.Viper
	flagSet *flag.FlagSet
}

// ConfigData defines the structure of the config data (e.g. in the config file)
type ConfigData struct {
	SocketDir       string                     `json:"socket_dir"`
	ServiceName     string                     `json:"service_name"`
	CoordinatorURL  string                     `json:"coordinator_url"`
	DefaultPriority uint                       `json:"default_priority"`
	LogLevel        string                     `json:"log_level"`
	DefaultTimeout  uint64                     `json:"default_timeout"`
	RequestTimeout  uint64                     `json:"request_timeout"`
	Tasks           map[string]*TaskConfigData `json:"tasks"`
}

// TaskConfigData defines the structure of the task config data (e.g. in the config file)
type TaskConfigData struct {
	Priority uint   `json:"priority"`
	Timeout  uint64 `json:"timeout"`
}

// NewConfig creates a new instance of Config. If a viper instance is not
// provided, a new one will be created.
func NewConfig(flagSet *flag.FlagSet, v *viper.Viper) *Config {
	if flagSet == nil {
		flagSet = flag.CommandLine
	}

	if v == nil {
		v = viper.New()
	}

	flagSet.StringP("config_file", "c", "", "path to config file")
	flagSet.StringP("service_name", "n", "", "provider service name")
	flagSet.StringP("socket_dir", "s", "/tmp/mistify", "base directory in which to create task sockets")
	flagSet.UintP("default_priority", "p", 50, "default task priority")
	flagSet.StringP("coordinator_url", "u", "", "url of coordinator for making requests")
	flagSet.StringP("log_level", "l", "warning", "log level: debug/info/warn/error/fatal/panic")
	flagSet.Uint64P("request_timeout", "t", 0, "default timeout for requests made by this provider in seconds")

	return &Config{
		viper:   v,
		flagSet: flagSet,
	}
}

// LoadConfig attempts to load the config. Flags should be parsed first.
func (c *Config) LoadConfig() error {
	if err := c.viper.BindPFlags(c.flagSet); err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Error("failed to bind flags")
		return err
	}

	filePath := c.viper.GetString("config_file")
	if filePath == "" {
		return c.Validate()
	}

	c.viper.SetConfigFile(filePath)
	if err := c.viper.ReadInConfig(); err != nil {
		log.WithFields(log.Fields{
			"error":    err,
			"filePath": filePath,
		}).Error("failed to parse config file")
		return err
	}

	return c.Validate()
}

// TaskPriority determines the registration priority of a task. If a
// priority was not explicitly configured for the task, it will return the
// default.
func (c *Config) TaskPriority(taskName string) int {
	key := fmt.Sprintf("tasks.%s.priority", taskName)
	if c.viper.IsSet(key) {
		return c.viper.GetInt(key)
	}
	return c.viper.GetInt("default_priority")
}

// TaskTimeout determines the timeout for a task. If a timeout was not
// explicitly configured for the task, it will return the default.
func (c *Config) TaskTimeout(taskName string) time.Duration {
	key := fmt.Sprintf("tasks.%s.timeout", taskName)
	var seconds int
	if c.viper.IsSet(key) {
		seconds = c.viper.GetInt(key)
	} else {
		seconds = c.viper.GetInt("default_timeout")
	}

	return time.Duration(seconds) * time.Second
}

// SocketDir returns the base directory for task sockets.
func (c *Config) SocketDir() string {
	return c.viper.GetString("socket_dir")
}

// StreamDir returns the directory for ad-hoc data stream sockets.
func (c *Config) StreamDir(taskName string) string {
	return filepath.Join(
		c.SocketDir(),
		"streams",
		taskName,
		c.ServiceName())
}

// ServiceName returns the name the service should register as.
func (c *Config) ServiceName() string {
	return c.viper.GetString("service_name")
}

// CoordinatorURL returns the URL of the Coordinator for which the Provider is
// registered.
func (c *Config) CoordinatorURL() *url.URL {
	// Error checking has been done during validation
	u, _ := url.ParseRequestURI(c.viper.GetString("coordinator_url"))
	return u
}

// RequestTimeout returns the duration of the default request timeout.
func (c *Config) RequestTimeout() time.Duration {
	return time.Second * time.Duration(c.viper.GetInt("request_timeout"))
}

// Validate returns whether the config is valid, containing necessary values.
func (c *Config) Validate() error {
	if c.SocketDir() == "" {
		err := errors.New("missing socket_dir")
		log.WithFields(log.Fields{
			"error": err,
		}).Error("invalid config")
		return err
	}

	if c.ServiceName() == "" {
		err := errors.New("missing service_name")
		log.WithFields(log.Fields{
			"error": err,
		}).Error("invalid config")
		return err
	}
	if _, err := url.ParseRequestURI(c.viper.GetString("coordinator_url")); err != nil {
		log.WithFields(log.Fields{
			"coordinator_url": c.viper.GetString("coordinator_url"),
			"error":           err,
		}).Error("invalid config")
		return err
	}

	return nil
}

// Unmarshal unmarshals the config into a struct.
func (c *Config) Unmarshal(rawVal interface{}) error {
	config := &mapstructure.DecoderConfig{
		Result:  rawVal,
		TagName: "json",
	}
	decoder, err := mapstructure.NewDecoder(config)
	if err != nil {
		return err
	}
	return decoder.Decode(c.viper.AllSettings())
}

// UnmarshalKey unmarshals a single config key into a struct.
func (c *Config) UnmarshalKey(key string, rawVal interface{}) error {
	return c.viper.UnmarshalKey(key, rawVal)
}

// SetupLogging sets the log level and formatting.
func (c *Config) SetupLogging() error {
	logLevel := c.viper.GetString("log_level")
	if err := logx.SetLevel(logLevel); err != nil {
		log.WithFields(log.Fields{
			"error": err,
			"level": logLevel,
		}).Error("failed to set up logging")
		return err
	}
	return nil
}
<commit_msg>Fix UnmarshalKey to use the json tag<commit_after>package provider

import (
	"errors"
	"fmt"
	"net/url"
	"path/filepath"
	"time"

	log "github.com/Sirupsen/logrus"
	logx "github.com/mistifyio/mistify-logrus-ext"
	"github.com/mitchellh/mapstructure"
	flag "github.com/spf13/pflag"
	"github.com/spf13/viper"
)

// Config holds all configuration for the provider.
type Config struct {
	viper   *viper.Viper
	flagSet *flag.FlagSet
}

// ConfigData defines the structure of the config data (e.g. in the config file)
type ConfigData struct {
	SocketDir       string                     `json:"socket_dir"`
	ServiceName     string                     `json:"service_name"`
	CoordinatorURL  string                     `json:"coordinator_url"`
	DefaultPriority uint                       `json:"default_priority"`
	LogLevel        string                     `json:"log_level"`
	DefaultTimeout  uint64                     `json:"default_timeout"`
	RequestTimeout  uint64                     `json:"request_timeout"`
	Tasks           map[string]*TaskConfigData `json:"tasks"`
}

// TaskConfigData defines the structure of the task config data (e.g. in the config file)
type TaskConfigData struct {
	Priority uint   `json:"priority"`
	Timeout  uint64 `json:"timeout"`
}

// NewConfig creates a new instance of Config. If a viper instance is not
// provided, a new one will be created.
func NewConfig(flagSet *flag.FlagSet, v *viper.Viper) *Config {
	if flagSet == nil {
		flagSet = flag.CommandLine
	}

	if v == nil {
		v = viper.New()
	}

	flagSet.StringP("config_file", "c", "", "path to config file")
	flagSet.StringP("service_name", "n", "", "provider service name")
	flagSet.StringP("socket_dir", "s", "/tmp/mistify", "base directory in which to create task sockets")
	flagSet.UintP("default_priority", "p", 50, "default task priority")
	flagSet.StringP("coordinator_url", "u", "", "url of coordinator for making requests")
	flagSet.StringP("log_level", "l", "warning", "log level: debug/info/warn/error/fatal/panic")
	flagSet.Uint64P("request_timeout", "t", 0, "default timeout for requests made by this provider in seconds")

	return &Config{
		viper:   v,
		flagSet: flagSet,
	}
}

// LoadConfig attempts to load the config. Flags should be parsed first.
func (c *Config) LoadConfig() error {
	if err := c.viper.BindPFlags(c.flagSet); err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Error("failed to bind flags")
		return err
	}

	filePath := c.viper.GetString("config_file")
	if filePath == "" {
		return c.Validate()
	}

	c.viper.SetConfigFile(filePath)
	if err := c.viper.ReadInConfig(); err != nil {
		log.WithFields(log.Fields{
			"error":    err,
			"filePath": filePath,
		}).Error("failed to parse config file")
		return err
	}

	return c.Validate()
}

// TaskPriority determines the registration priority of a task. If a
// priority was not explicitly configured for the task, it will return the
// default.
func (c *Config) TaskPriority(taskName string) int {
	key := fmt.Sprintf("tasks.%s.priority", taskName)
	if c.viper.IsSet(key) {
		return c.viper.GetInt(key)
	}
	return c.viper.GetInt("default_priority")
}

// TaskTimeout determines the timeout for a task. If a timeout was not
// explicitly configured for the task, it will return the default.
func (c *Config) TaskTimeout(taskName string) time.Duration {
	key := fmt.Sprintf("tasks.%s.timeout", taskName)
	var seconds int
	if c.viper.IsSet(key) {
		seconds = c.viper.GetInt(key)
	} else {
		seconds = c.viper.GetInt("default_timeout")
	}

	return time.Duration(seconds) * time.Second
}

// SocketDir returns the base directory for task sockets.
func (c *Config) SocketDir() string {
	return c.viper.GetString("socket_dir")
}

// StreamDir returns the directory for ad-hoc data stream sockets.
func (c *Config) StreamDir(taskName string) string {
	return filepath.Join(
		c.SocketDir(),
		"streams",
		taskName,
		c.ServiceName())
}

// ServiceName returns the name the service should register as.
func (c *Config) ServiceName() string {
	return c.viper.GetString("service_name")
}

// CoordinatorURL returns the URL of the Coordinator for which the Provider is
// registered.
func (c *Config) CoordinatorURL() *url.URL {
	// Error checking has been done during validation
	u, _ := url.ParseRequestURI(c.viper.GetString("coordinator_url"))
	return u
}

// RequestTimeout returns the duration of the default request timeout.
func (c *Config) RequestTimeout() time.Duration {
	return time.Second * time.Duration(c.viper.GetInt("request_timeout"))
}

// Validate returns whether the config is valid, containing necessary values.
func (c *Config) Validate() error {
	if c.SocketDir() == "" {
		err := errors.New("missing socket_dir")
		log.WithFields(log.Fields{
			"error": err,
		}).Error("invalid config")
		return err
	}

	if c.ServiceName() == "" {
		err := errors.New("missing service_name")
		log.WithFields(log.Fields{
			"error": err,
		}).Error("invalid config")
		return err
	}
	if _, err := url.ParseRequestURI(c.viper.GetString("coordinator_url")); err != nil {
		log.WithFields(log.Fields{
			"coordinator_url": c.viper.GetString("coordinator_url"),
			"error":           err,
		}).Error("invalid config")
		return err
	}

	return nil
}

// Unmarshal unmarshals the config into a struct.
func (c *Config) Unmarshal(rawVal interface{}) error {
	config := &mapstructure.DecoderConfig{
		Result:  rawVal,
		TagName: "json",
	}
	decoder, err := mapstructure.NewDecoder(config)
	if err != nil {
		return err
	}
	return decoder.Decode(c.viper.AllSettings())
}

// UnmarshalKey unmarshals a single config key into a struct.
func (c *Config) UnmarshalKey(key string, rawVal interface{}) error {
	config := &mapstructure.DecoderConfig{
		Result:  rawVal,
		TagName: "json",
	}
	decoder, err := mapstructure.NewDecoder(config)
	if err != nil {
		return err
	}
	return decoder.Decode(c.viper.Get(key))
}

// SetupLogging sets the log level and formatting.
func (c *Config) SetupLogging() error {
	logLevel := c.viper.GetString("log_level")
	if err := logx.SetLevel(logLevel); err != nil {
		log.WithFields(log.Fields{
			"error": err,
			"level": logLevel,
		}).Error("failed to set up logging")
		return err
	}
	return nil
}
<|endoftext|>