<commit_before>/*
 * Logging-related functions.
 *
 * (c) 2011-2012 Bernd Fix   >Y<
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package logger

///////////////////////////////////////////////////////////////////////
// Import external declarations

import (
	"fmt"
	"os"
	"time"
)

///////////////////////////////////////////////////////////////////////
// Logging constants

const (
	CRITICAL = iota // critical errors
	SEVERE          // severe errors
	ERROR           // errors
	WARN            // warnings
	INFO            // info
	DBG_HIGH        // debug (high prio)
	DBG             // debug (normal)
	DBG_ALL         // debug (all)

	cmd_ROTATE = iota // rotate log file
)

///////////////////////////////////////////////////////////////////////
// Local types

type logger struct {
	msgChan chan string // message to be logged
	cmdChan chan int    // commands to be executed
	logfile *os.File    // current log file (can be stdout/stderr)
	started time.Time   // start time of current log file
	level   int         // current log level
}

///////////////////////////////////////////////////////////////////////
// Local variables

var (
	logInst *logger = nil // singleton logger instance
)

///////////////////////////////////////////////////////////////////////
// Logger-internal methods / functions
/*
 * Instantiate new logger (to stdout) and run its handler loop.
 */
func init() {
	logInst = new(logger)
	logInst.msgChan = make(chan string)
	logInst.cmdChan = make(chan int)
	logInst.logfile = os.Stdout
	logInst.started = time.Now()
	logInst.level = DBG

	go func(){
		for {
			select {
			case msg := <-logInst.msgChan:
				ts := time.Now().Format(time.Stamp)
				logInst.logfile.WriteString(ts + msg)
			case cmd := <-logInst.cmdChan:
				switch cmd {
				case cmd_ROTATE:
					if logInst.logfile != os.Stdout {
						fname := logInst.logfile.Name()
						logInst.logfile.Close()
						logInst.logfile = nil
						ts := logInst.started.Format(time.RFC3339)
						os.Rename(fname, fname+"."+ts)
						LogToFile(fname)
					} else {
						Println(WARN, "[log] log rotation for 'stdout' not applicable.")
					}
				}
			}
		}
	}()
}

///////////////////////////////////////////////////////////////////////
// Public logging functions.

/*
 * Punch logging data for given level.
 * @param level int - associated logging level
 * @param line string - information to be logged
 */
func Println(level int, line string) {
	if level <= logInst.level {
		logInst.msgChan <- getTag(level) + line + "\n"
	}
}

//---------------------------------------------------------------------
/*
 * Punch formatted logging data for givel level
 * @param level int - associated logging level
 * @param format string - format definition
 * @param v ...interface{} - list of variables to be formatted
 */
func Printf(level int, format string, v ...interface{}) {
	if level <= logInst.level {
		logInst.msgChan <- getTag(level) + fmt.Sprintf(format, v...)
	}
}

//=====================================================================
// Logfile functions
//=====================================================================

/*
 * Start logging to file.
 * @param filename string - name of logfile
 */
func LogToFile(filename string) bool {
	if logInst.logfile == nil {
		logInst.logfile = os.Stdout
	}
	Println(INFO, "[log] file-based logging to '"+filename+"'")
	if f, err := os.Create(filename); err == nil {
		logInst.logfile = f
		logInst.started = time.Now()
		return true
	}
	Println(ERROR, "[log] can't enable file-based logging!")
	return false
}

//---------------------------------------------------------------------
/*
 * Rotate log file.
 */
func Rotate() {
	logInst.cmdChan <- cmd_ROTATE
}

//=====================================================================
// Human-readable log tags
//=====================================================================

/*
 * Return numeric log level.
 * @return int - current log level
 */
func GetLogLevel() int {
	return logInst.level
}

//---------------------------------------------------------------------
/*
 * Get current loglevel in human-readable form.
 * @return string - symbolic name of loglevel
 */
func GetLogLevelName() string {
	switch logInst.level {
	case CRITICAL:
		return "CRITICAL"
	case SEVERE:
		return "SEVERE"
	case ERROR:
		return "ERROR"
	case WARN:
		return "WARN"
	case INFO:
		return "INFO"
	case DBG_HIGH:
		return "DBG_HIGH"
	case DBG:
		return "DBG"
	case DBG_ALL:
		return "DBG_ALL"
	}
	return "UNKNOWN_LOGLEVEL"
}

//---------------------------------------------------------------------
/*
 * Set logging level from value
 * @param lvl int - new log level
 */
func SetLogLevel(lvl int) {
	if lvl < CRITICAL || lvl > DBG_ALL {
		Printf(WARN, "[logger] Unknown loglevel '%d' requested -- ignored.\n", lvl)
	}
	logInst.level = lvl
}

//---------------------------------------------------------------------
/*
 * Set logging level from symbolic name.
 * @param name string - name of log level
 */
func SetLogLevelFromName(name string) {
	switch name {
	case "ERROR":
		logInst.level = ERROR
	case "WARN":
		logInst.level = WARN
	case "INFO":
		logInst.level = INFO
	case "DBG_HIGH":
		logInst.level = DBG_HIGH
	case "DBG":
		logInst.level = DBG
	case "DBG_ALL":
		logInst.level = DBG_ALL
	default:
		Println(WARN, "[logger] Unknown loglevel '"+name+"' requested.")
	}
}

//---------------------------------------------------------------------
/*
 * Get loglevel tag as prefix for message
 * @param level int - log level
 * @return string - log tag
 */
func getTag(level int) string {
	switch level {
	case CRITICAL:
		return "{C}"
	case SEVERE:
		return "{S}"
	case ERROR:
		return "{E}"
	case WARN:
		return "{W}"
	case INFO:
		return "{I}"
	case DBG_HIGH:
		return "{D2}"
	case DBG:
		return "{D1}"
	case DBG_ALL:
		return "{D0}"
	}
	return "{?}"
}
<commit_msg>Fallback to os.Stdout in log rotation if logfile creation fails.<commit_after>/*
 * Logging-related functions.
 *
 * (c) 2011-2012 Bernd Fix   >Y<
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package logger

///////////////////////////////////////////////////////////////////////
// Import external declarations

import (
	"fmt"
	"os"
	"time"
)

///////////////////////////////////////////////////////////////////////
// Logging constants

const (
	CRITICAL = iota // critical errors
	SEVERE          // severe errors
	ERROR           // errors
	WARN            // warnings
	INFO            // info
	DBG_HIGH        // debug (high prio)
	DBG             // debug (normal)
	DBG_ALL         // debug (all)

	cmd_ROTATE = iota // rotate log file
)

///////////////////////////////////////////////////////////////////////
// Local types

type logger struct {
	msgChan chan string // message to be logged
	cmdChan chan int    // commands to be executed
	logfile *os.File    // current log file (can be stdout/stderr)
	started time.Time   // start time of current log file
	level   int         // current log level
}

///////////////////////////////////////////////////////////////////////
// Local variables

var (
	logInst *logger = nil // singleton logger instance
)

///////////////////////////////////////////////////////////////////////
// Logger-internal methods / functions
/*
 * Instantiate new logger (to stdout) and run its handler loop.
 */
func init() {
	logInst = new(logger)
	logInst.msgChan = make(chan string)
	logInst.cmdChan = make(chan int)
	logInst.logfile = os.Stdout
	logInst.started = time.Now()
	logInst.level = DBG

	go func(){
		for {
			select {
			case msg := <-logInst.msgChan:
				ts := time.Now().Format(time.Stamp)
				logInst.logfile.WriteString(ts + msg)
			case cmd := <-logInst.cmdChan:
				switch cmd {
				case cmd_ROTATE:
					if logInst.logfile != os.Stdout {
						fname := logInst.logfile.Name()
						logInst.logfile.Close()
						ts := logInst.started.Format(time.RFC3339)
						os.Rename(fname, fname+"."+ts)
						var err error
						if logInst.logfile, err = os.Create(fname); err != nil {
							logInst.logfile = os.Stdout
						}
						logInst.started = time.Now()
					} else {
						Println(WARN, "[log] log rotation for 'stdout' not applicable.")
					}
				}
			}
		}
	}()
}

///////////////////////////////////////////////////////////////////////
// Public logging functions.

/*
 * Punch logging data for given level.
 * @param level int - associated logging level
 * @param line string - information to be logged
 */
func Println(level int, line string) {
	if level <= logInst.level {
		logInst.msgChan <- getTag(level) + line + "\n"
	}
}

//---------------------------------------------------------------------
/*
 * Punch formatted logging data for givel level
 * @param level int - associated logging level
 * @param format string - format definition
 * @param v ...interface{} - list of variables to be formatted
 */
func Printf(level int, format string, v ...interface{}) {
	if level <= logInst.level {
		logInst.msgChan <- getTag(level) + fmt.Sprintf(format, v...)
	}
}

//=====================================================================
// Logfile functions
//=====================================================================

/*
 * Start logging to file.
 * @param filename string - name of logfile
 */
func LogToFile(filename string) bool {
	if logInst.logfile == nil {
		logInst.logfile = os.Stdout
	}
	Println(INFO, "[log] file-based logging to '"+filename+"'")
	if f, err := os.Create(filename); err == nil {
		logInst.logfile = f
		logInst.started = time.Now()
		return true
	}
	Println(ERROR, "[log] can't enable file-based logging!")
	return false
}

//---------------------------------------------------------------------
/*
 * Rotate log file.
 */
func Rotate() {
	logInst.cmdChan <- cmd_ROTATE
}

//=====================================================================
// Human-readable log tags
//=====================================================================

/*
 * Return numeric log level.
 * @return int - current log level
 */
func GetLogLevel() int {
	return logInst.level
}

//---------------------------------------------------------------------
/*
 * Get current loglevel in human-readable form.
 * @return string - symbolic name of loglevel
 */
func GetLogLevelName() string {
	switch logInst.level {
	case CRITICAL:
		return "CRITICAL"
	case SEVERE:
		return "SEVERE"
	case ERROR:
		return "ERROR"
	case WARN:
		return "WARN"
	case INFO:
		return "INFO"
	case DBG_HIGH:
		return "DBG_HIGH"
	case DBG:
		return "DBG"
	case DBG_ALL:
		return "DBG_ALL"
	}
	return "UNKNOWN_LOGLEVEL"
}

//---------------------------------------------------------------------
/*
 * Set logging level from value
 * @param lvl int - new log level
 */
func SetLogLevel(lvl int) {
	if lvl < CRITICAL || lvl > DBG_ALL {
		Printf(WARN, "[logger] Unknown loglevel '%d' requested -- ignored.\n", lvl)
	}
	logInst.level = lvl
}

//---------------------------------------------------------------------
/*
 * Set logging level from symbolic name.
 * @param name string - name of log level
 */
func SetLogLevelFromName(name string) {
	switch name {
	case "ERROR":
		logInst.level = ERROR
	case "WARN":
		logInst.level = WARN
	case "INFO":
		logInst.level = INFO
	case "DBG_HIGH":
		logInst.level = DBG_HIGH
	case "DBG":
		logInst.level = DBG
	case "DBG_ALL":
		logInst.level = DBG_ALL
	default:
		Println(WARN, "[logger] Unknown loglevel '"+name+"' requested.")
	}
}

//---------------------------------------------------------------------
/*
 * Get loglevel tag as prefix for message
 * @param level int - log level
 * @return string - log tag
 */
func getTag(level int) string {
	switch level {
	case CRITICAL:
		return "{C}"
	case SEVERE:
		return "{S}"
	case ERROR:
		return "{E}"
	case WARN:
		return "{W}"
	case INFO:
		return "{I}"
	case DBG_HIGH:
		return "{D2}"
	case DBG:
		return "{D1}"
	case DBG_ALL:
		return "{D0}"
	}
	return "{?}"
}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "github.com/voxelbrain/goptions"
    "github.com/prasmussen/gandi/shared"
    "github.com/prasmussen/gandi/gandi-domain-zone/cli"
    api "github.com/prasmussen/gandi-api/domain/zone"
)

type Options struct {
    Testing bool `goptions:"-t, --testing, description='Perform queries against the test platform (OT&E)'"`
    ConfigPath string `goptions:"-c, --config, description='Set config path. Defaults to ~/.gandi/config'"`
    Version bool `goptions:"-v, --version, description='Print version'"`
    goptions.Help `goptions:"-h, --help, description='Show this help'"`

    goptions.Verbs

    Count shared.NoArgs `goptions:"count"`
    List shared.NoArgs `goptions:"list"`

    Info struct {
        Zone int64 `goptions:"-z, --zone, obligatory, description='Zone id'"`
    } `goptions:"info"`

    Create struct {
        Name string `goptions:"-n, --name, obligatory, description='Zone name'"`
    } `goptions:"create"`

    Delete struct {
        Zone int64 `goptions:"-z, --zone, obligatory, description='Zone id'"`
    } `goptions:"delete"`

    Set struct {
        Zone int64 `goptions:"-z, --zone, obligatory, description='Zone id'"`
        Name string `goptions:"-n, --name, obligatory, description='Domain name'"`
    } `goptions:"set"`
}

func main() {
    opts := &Options{}
    goptions.ParseAndFail(opts)

    // Print version number and exit if the version flag is set
    if opts.Version {
        fmt.Printf("gandi-domain-zone v%s\n", shared.VersionNumber)
        return
    }

    // Get gandi client
    client := shared.NewGandiClient(opts.ConfigPath, opts.Testing)

    // Create api and zone instances
    api := api.New(client)
    zone := cli.New(api)

    switch opts.Verbs {
        case "count":
            zone.Count()

        case "list":
            zone.List()

        case "info":
            zone.Info(opts.Info.Zone)

        case "create":
            zone.Create(opts.Create.Name)

        case "delete":
            zone.Delete(opts.Delete.Zone)

        case "set":
            zone.Set(opts.Set.Name, opts.Set.Zone)

        default:
            goptions.PrintHelp()
    }
}

<commit_msg>Flag rename<commit_after>package main

import (
    "fmt"
    "github.com/voxelbrain/goptions"
    "github.com/prasmussen/gandi/shared"
    "github.com/prasmussen/gandi/gandi-domain-zone/cli"
    api "github.com/prasmussen/gandi-api/domain/zone"
)

type Options struct {
    Testing bool `goptions:"-t, --testing, description='Perform queries against the test platform (OT&E)'"`
    ConfigPath string `goptions:"-c, --config, description='Set config path. Defaults to ~/.gandi/config'"`
    Version bool `goptions:"-v, --version, description='Print version'"`
    goptions.Help `goptions:"-h, --help, description='Show this help'"`

    goptions.Verbs

    Count shared.NoArgs `goptions:"count"`
    List shared.NoArgs `goptions:"list"`

    Info struct {
        Zone int64 `goptions:"-z, --zone, obligatory, description='Zone id'"`
    } `goptions:"info"`

    Create struct {
        Name string `goptions:"-n, --name, obligatory, description='Zone name'"`
    } `goptions:"create"`

    Delete struct {
        Zone int64 `goptions:"-z, --zone, obligatory, description='Zone id'"`
    } `goptions:"delete"`

    Set struct {
        Zone int64 `goptions:"-z, --zone, obligatory, description='Zone id'"`
        Domain string `goptions:"-d, --domain, obligatory, description='Domain name'"`
    } `goptions:"set"`
}

func main() {
    opts := &Options{}
    goptions.ParseAndFail(opts)

    // Print version number and exit if the version flag is set
    if opts.Version {
        fmt.Printf("gandi-domain-zone v%s\n", shared.VersionNumber)
        return
    }

    // Get gandi client
    client := shared.NewGandiClient(opts.ConfigPath, opts.Testing)

    // Create api and zone instances
    api := api.New(client)
    zone := cli.New(api)

    switch opts.Verbs {
        case "count":
            zone.Count()

        case "list":
            zone.List()

        case "info":
            zone.Info(opts.Info.Zone)

        case "create":
            zone.Create(opts.Create.Name)

        case "delete":
            zone.Delete(opts.Delete.Zone)

        case "set":
            zone.Set(opts.Set.Domain, opts.Set.Zone)

        default:
            goptions.PrintHelp()
    }
}

<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"encoding/xml"
	"flag"
	irc "github.com/fluffle/goirc/client"
	_ "github.com/go-sql-driver/mysql"
	"github.com/tv42/base58"
	"html"
	"io"
	"io/ioutil"
	"log"
	"math/big"
	"math/rand"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

var (
	config                  Config
	urlRegex, regexErr      = regexp.Compile(`(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s` + "`" + `!()\[\]{};:'".,<>?«»“”‘’]))`)
	httpRegex, httpRegexErr = regexp.Compile(`http(s)?://.*`)
	flickrApiUrl            = "http://api.flickr.com/services/rest/"
)

type Config struct {
	Channel      string
	DBConn       string
	Nick         string
	Ident        string
	FullName     string
	FlickrAPIKey string
	IRCPass      string
	Commands     []Command `xml:">Command"`
}

type Command struct {
	Name string
	Text string
}

type Setresp struct {
	Sets []Set `xml:"collections>collection>set"`
}

type Set struct {
	Id          string `xml:"id,attr"`
	Title       string `xml:"title,attr"`
	Description string `xml:"description,attr"`
}

type Photoresp struct {
	Photos []Photo `xml:"photoset>photo"`
}

type Photo struct {
	Id        int64  `xml:"id,attr"`
	Secret    string `xml:"secret,attr"`
	Server    string `xml:"server,attr"`
	Farm      string `xml:"farm,attr"`
	Title     string `xml:"title,attr"`
	Isprimary string `xml:"isprimary,attr"`
}

func htmlfetch(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	respbody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return respbody, nil
}

func random(limit int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(limit)
}

func sendUrl(channel, postedUrl string, conn *irc.Conn) {
	log.Println("Fetching title for " + postedUrl + " In channel " + channel)
	if !httpRegex.MatchString(postedUrl) {
		postedUrl = "http://" + postedUrl
	}

	resp, err := http.Get(postedUrl)
	if err != nil {
		log.Println(err)
		return
	}
	defer resp.Body.Close()
	buf := make([]byte, 1024)
	respbody := []byte{}
	for i := 0; i < 30; i++ {
		n, err := resp.Body.Read(buf)
		if err != nil && err != io.EOF {
			return
		}
		if n == 0 {
			break
		}
		respbody = append(respbody, buf[:n]...)
	}

	stringbody := string(respbody)
	titlestart := strings.Index(stringbody, "<title>")
	titleend := strings.Index(stringbody, "</title>")
	if titlestart != -1 && titlestart != -1 {
		title := string(respbody[titlestart+7 : titleend])
		title = strings.TrimSpace(title)
		if title != "" {
			parsedurl, err := url.Parse(postedUrl)
			if err == nil {
				postedUrl = parsedurl.Host
			}
			title = "Title: " + html.UnescapeString(title) + " (at " + postedUrl + ")"
			log.Println(title)
			conn.Privmsg(channel, title)
		}
	}
}

func dance(channel string, conn *irc.Conn) {
	conn.Privmsg(channel, ":D-<")
	time.Sleep(500 * time.Millisecond)
	conn.Privmsg(channel, ":D|<")
	time.Sleep(500 * time.Millisecond)
	conn.Privmsg(channel, ":D/<")
}

func haata(channel string, conn *irc.Conn) {
	flickrUrl, err := url.Parse(flickrApiUrl)
	if err != nil {
		log.Println(err)
		return
	}
	v := flickrUrl.Query()
	v.Set("method", "flickr.collections.getTree")
	v.Set("api_key", config.FlickrAPIKey)
	v.Set("user_id", "57321699@N06")
	v.Set("collection_id", "57276377-72157635417889224")
	flickrUrl.RawQuery = v.Encode()

	sets, err := htmlfetch(flickrUrl.String())
	if err != nil {
		log.Println(err)
		return
	}
	var setresp Setresp
	xml.Unmarshal(sets, &setresp)
	randsetindex := random(len(setresp.Sets))
	randset := setresp.Sets[randsetindex].Id

	flickrUrl, err = url.Parse(flickrApiUrl)
	if err != nil {
		log.Println(err)
		return
	}
	v = flickrUrl.Query()
	v.Set("method", "flickr.photosets.getPhotos")
	v.Set("api_key", config.FlickrAPIKey)
	v.Set("photoset_id", randset)
	flickrUrl.RawQuery = v.Encode()

	pics, err := htmlfetch(flickrUrl.String())
	if err != nil {
		log.Println(err)
		return
	}
	var photoresp Photoresp
	xml.Unmarshal(pics, &photoresp)
	randpic := random(len(photoresp.Photos))
	photostring := string(base58.EncodeBig([]byte{}, big.NewInt(photoresp.Photos[randpic].Id)))
	conn.Privmsg(channel, strings.TrimSpace(setresp.Sets[randsetindex].Title)+`: http://flic.kr/p/`+photostring)
}

func handleMessage(conn *irc.Conn, line *irc.Line) {
	urllist := []string{}
	numlinks := 0

	// Special commands
	if strings.HasPrefix(line.Args[1], "!dance") && line.Nick == "sadbox" {
		go dance(line.Args[0], conn)
	} else if strings.HasPrefix(line.Args[1], "!audio") && line.Nick == "sadbox" {
		conn.Privmsg(line.Args[0], "https://sadbox.org/static/audiophile.html")
	} else if strings.HasPrefix(line.Args[1], "!cst") && line.Nick == "sadbox" {
		conn.Privmsg(line.Args[0], "13,8#CSTMASTERRACE")
	} else if strings.HasPrefix(line.Args[1], "!haata") {
		go haata(line.Args[0], conn)
	}

	// Commands that are read in from the config file
	for _, command := range config.Commands {
		if strings.HasPrefix(line.Args[1], command.Name) {
			conn.Privmsg(line.Args[0], command.Text)
		}
	}

NextWord:
	for _, word := range strings.Split(line.Args[1], " ") {
		word = strings.TrimSpace(word)
		if urlRegex.MatchString(word) {
			for _, subUrl := range urllist {
				if subUrl == word {
					continue NextWord
				}
			}
			numlinks++
			if numlinks > 3 {
				break
			}
			urllist = append(urllist, word)
			go sendUrl(line.Args[0], word, conn)
		}

	}
	db, err := sql.Open("mysql", config.DBConn)
	if err != nil {
		log.Println(err)
	}
	defer db.Close()
	_, err = db.Exec("insert into messages (Nick, Ident, Host, Src, Cmd, Channel, Message, Time) values (?, ?, ?, ?, ?, ?, ?, ?)", line.Nick, line.Ident, line.Host, line.Src, line.Cmd, line.Args[0], line.Args[1], line.Time)
	if err != nil {
		log.Println(err)
	}
}

func init() {
	log.Println("Starting sadbot")

	flag.Parse()

	if regexErr != nil {
		log.Panic(regexErr)
	}
	if httpRegexErr != nil {
		log.Panic(httpRegexErr)
	}

	xmlFile, err := ioutil.ReadFile("config.xml")
	if err != nil {
		log.Fatal(err)
	}
	xml.Unmarshal(xmlFile, &config)
}

func main() {
	log.Printf("Joining channel %s", config.Channel)
	log.Printf("Nick: %s Ident: %s FullName: %s", config.Nick, config.Ident, config.FullName)

	log.Printf("Loading %d commands", len(config.Commands))
	for index, command := range config.Commands {
		log.Printf("%d %s: %s", index+1, command.Name, command.Text)
	}
	log.Printf("Finished loading commands")

	c := irc.SimpleClient(config.Nick, config.Ident, config.FullName)

	c.AddHandler(irc.CONNECTED,
		func(conn *irc.Conn, line *irc.Line) {
			conn.Join(config.Channel)
			conn.Privmsg("nickserv", "identify "+config.Nick+" "+config.IRCPass)
			log.Println("Connected!")
		})

	quit := make(chan bool)

	c.AddHandler(irc.DISCONNECTED,
		func(conn *irc.Conn, line *irc.Line) { quit <- true })

	c.AddHandler("PRIVMSG", handleMessage)

	if err := c.Connect("irc.freenode.net"); err != nil {
		log.Fatalln("Connection error: %s\n", err)
	}

	<-quit
}
<commit_msg>moved flickr api url to be a const<commit_after>package main

import (
	"database/sql"
	"encoding/xml"
	"flag"
	irc "github.com/fluffle/goirc/client"
	_ "github.com/go-sql-driver/mysql"
	"github.com/tv42/base58"
	"html"
	"io"
	"io/ioutil"
	"log"
	"math/big"
	"math/rand"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

var (
	config                  Config
	urlRegex, regexErr      = regexp.Compile(`(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s` + "`" + `!()\[\]{};:'".,<>?«»“”‘’]))`)
	httpRegex, httpRegexErr = regexp.Compile(`http(s)?://.*`)
)

const flickrApiUrl = "http://api.flickr.com/services/rest/"

type Config struct {
	Channel      string
	DBConn       string
	Nick         string
	Ident        string
	FullName     string
	FlickrAPIKey string
	IRCPass      string
	Commands     []Command `xml:">Command"`
}

type Command struct {
	Name string
	Text string
}

type Setresp struct {
	Sets []Set `xml:"collections>collection>set"`
}

type Set struct {
	Id          string `xml:"id,attr"`
	Title       string `xml:"title,attr"`
	Description string `xml:"description,attr"`
}

type Photoresp struct {
	Photos []Photo `xml:"photoset>photo"`
}

type Photo struct {
	Id        int64  `xml:"id,attr"`
	Secret    string `xml:"secret,attr"`
	Server    string `xml:"server,attr"`
	Farm      string `xml:"farm,attr"`
	Title     string `xml:"title,attr"`
	Isprimary string `xml:"isprimary,attr"`
}

func htmlfetch(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	respbody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return respbody, nil
}

func random(limit int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(limit)
}

func sendUrl(channel, postedUrl string, conn *irc.Conn) {
	log.Println("Fetching title for " + postedUrl + " In channel " + channel)
	if !httpRegex.MatchString(postedUrl) {
		postedUrl = "http://" + postedUrl
	}

	resp, err := http.Get(postedUrl)
	if err != nil {
		log.Println(err)
		return
	}
	defer resp.Body.Close()
	buf := make([]byte, 1024)
	respbody := []byte{}
	for i := 0; i < 30; i++ {
		n, err := resp.Body.Read(buf)
		if err != nil && err != io.EOF {
			return
		}
		if n == 0 {
			break
		}
		respbody = append(respbody, buf[:n]...)
	}

	stringbody := string(respbody)
	titlestart := strings.Index(stringbody, "<title>")
	titleend := strings.Index(stringbody, "</title>")
	if titlestart != -1 && titlestart != -1 {
		title := string(respbody[titlestart+7 : titleend])
		title = strings.TrimSpace(title)
		if title != "" {
			parsedurl, err := url.Parse(postedUrl)
			if err == nil {
				postedUrl = parsedurl.Host
			}
			title = "Title: " + html.UnescapeString(title) + " (at " + postedUrl + ")"
			log.Println(title)
			conn.Privmsg(channel, title)
		}
	}
}

func dance(channel string, conn *irc.Conn) {
	conn.Privmsg(channel, ":D-<")
	time.Sleep(500 * time.Millisecond)
	conn.Privmsg(channel, ":D|<")
	time.Sleep(500 * time.Millisecond)
	conn.Privmsg(channel, ":D/<")
}

func haata(channel string, conn *irc.Conn) {
	flickrUrl, err := url.Parse(flickrApiUrl)
	if err != nil {
		log.Println(err)
		return
	}
	v := flickrUrl.Query()
	v.Set("method", "flickr.collections.getTree")
	v.Set("api_key", config.FlickrAPIKey)
	v.Set("user_id", "57321699@N06")
	v.Set("collection_id", "57276377-72157635417889224")
	flickrUrl.RawQuery = v.Encode()

	sets, err := htmlfetch(flickrUrl.String())
	if err != nil {
		log.Println(err)
		return
	}
	var setresp Setresp
	xml.Unmarshal(sets, &setresp)
	randsetindex := random(len(setresp.Sets))
	randset := setresp.Sets[randsetindex].Id

	flickrUrl, err = url.Parse(flickrApiUrl)
	if err != nil {
		log.Println(err)
		return
	}
	v = flickrUrl.Query()
	v.Set("method", "flickr.photosets.getPhotos")
	v.Set("api_key", config.FlickrAPIKey)
	v.Set("photoset_id", randset)
	flickrUrl.RawQuery = v.Encode()

	pics, err := htmlfetch(flickrUrl.String())
	if err != nil {
		log.Println(err)
		return
	}
	var photoresp Photoresp
	xml.Unmarshal(pics, &photoresp)
	randpic := random(len(photoresp.Photos))
	photostring := string(base58.EncodeBig([]byte{}, big.NewInt(photoresp.Photos[randpic].Id)))
	conn.Privmsg(channel, strings.TrimSpace(setresp.Sets[randsetindex].Title)+`: http://flic.kr/p/`+photostring)
}

func handleMessage(conn *irc.Conn, line *irc.Line) {
	urllist := []string{}
	numlinks := 0

	// Special commands
	if strings.HasPrefix(line.Args[1], "!dance") && line.Nick == "sadbox" {
		go dance(line.Args[0], conn)
	} else if strings.HasPrefix(line.Args[1], "!audio") && line.Nick == "sadbox" {
		conn.Privmsg(line.Args[0], "https://sadbox.org/static/audiophile.html")
	} else if strings.HasPrefix(line.Args[1], "!cst") && line.Nick == "sadbox" {
		conn.Privmsg(line.Args[0], "13,8#CSTMASTERRACE")
	} else if strings.HasPrefix(line.Args[1], "!haata") {
		go haata(line.Args[0], conn)
	}

	// Commands that are read in from the config file
	for _, command := range config.Commands {
		if strings.HasPrefix(line.Args[1], command.Name) {
			conn.Privmsg(line.Args[0], command.Text)
		}
	}

NextWord:
	for _, word := range strings.Split(line.Args[1], " ") {
		word = strings.TrimSpace(word)
		if urlRegex.MatchString(word) {
			for _, subUrl := range urllist {
				if subUrl == word {
					continue NextWord
				}
			}
			numlinks++
			if numlinks > 3 {
				break
			}
			urllist = append(urllist, word)
			go sendUrl(line.Args[0], word, conn)
		}

	}
	db, err := sql.Open("mysql", config.DBConn)
	if err != nil {
		log.Println(err)
	}
	defer db.Close()
	_, err = db.Exec("insert into messages (Nick, Ident, Host, Src, Cmd, Channel, Message, Time) values (?, ?, ?, ?, ?, ?, ?, ?)", line.Nick, line.Ident, line.Host, line.Src, line.Cmd, line.Args[0], line.Args[1], line.Time)
	if err != nil {
		log.Println(err)
	}
}

func init() {
	log.Println("Starting sadbot")

	flag.Parse()

	if regexErr != nil {
		log.Panic(regexErr)
	}
	if httpRegexErr != nil {
		log.Panic(httpRegexErr)
	}

	xmlFile, err := ioutil.ReadFile("config.xml")
	if err != nil {
		log.Fatal(err)
	}
	xml.Unmarshal(xmlFile, &config)
}

func main() {
	log.Printf("Joining channel %s", config.Channel)
	log.Printf("Nick: %s Ident: %s FullName: %s", config.Nick, config.Ident, config.FullName)

	log.Printf("Loading %d commands", len(config.Commands))
	for index, command := range config.Commands {
		log.Printf("%d %s: %s", index+1, command.Name, command.Text)
	}
	log.Printf("Finished loading commands")

	c := irc.SimpleClient(config.Nick, config.Ident, config.FullName)

	c.AddHandler(irc.CONNECTED,
		func(conn *irc.Conn, line *irc.Line) {
			conn.Join(config.Channel)
			conn.Privmsg("nickserv", "identify "+config.Nick+" "+config.IRCPass)
			log.Println("Connected!")
		})

	quit := make(chan bool)

	c.AddHandler(irc.DISCONNECTED,
		func(conn *irc.Conn, line *irc.Line) { quit <- true })

	c.AddHandler("PRIVMSG", handleMessage)

	if err := c.Connect("irc.freenode.net"); err != nil {
		log.Fatalln("Connection error: %s\n", err)
	}

	<-quit
}
<|endoftext|>


<commit_before>package cli

import (
	"flag"
	"testing"

	rpcpb "github.com/google/shipshape/shipshape/proto/shipshape_rpc_proto"
)

var dockerTag = flag.String("shipshape_test_docker_tag", "", "the docker tag for the images to use for testing")

func countFailures(resp rpcpb.ShipshapeResponse) int {
	failures := 0
	for _, analyzeResp := range resp.AnalyzeResponse {
		failures += len(analyzeResp.Failure)
	}
	return failures
}

func countNotes(resp rpcpb.ShipshapeResponse) int {
	notes := 0
	for _, analyzeResp := range resp.AnalyzeResponse {
		notes += len(analyzeResp.Note)
	}
	return notes
}

func countCategoryNotes(resp rpcpb.ShipshapeResponse, category string) int {
	notes := 0
	for _, analyzeResp := range resp.AnalyzeResponse {
		for _, note := range analyzeResp.Note {
			if *note.Category == category {
				notes += 1
			}
		}
	}
	return notes
}

func TestBasic(t *testing.T) {
	options := Options{
		File:                "shipshape/cli/testdata/workspace1",
		ThirdPartyAnalyzers: []string{},
		Build:               "maven",
		TriggerCats:         []string{"PostMessage", "JSHint"},
		Dind:                false,
		Event:               "manual", // TODO: const
		Repo:                "gcr.io/shipshape_releases", // TODO: const
		StayUp:              true,
		Tag:                 *dockerTag,
		// TODO(rsk): current e2e test can be ru both with & without kythe.
		LocalKythe:          false,
	}
	var allResponses rpcpb.ShipshapeResponse
	options.HandleResponse = func(shipshapeResp *rpcpb.ShipshapeResponse, _ string) error {
		allResponses.AnalyzeResponse = append(allResponses.AnalyzeResponse, shipshapeResp.AnalyzeResponse...)
		return nil
	}

	returnedNotesCount, err := New(options).Run()
	if err != nil {
		t.Fatal(err)
	}
	testName := "TestBasic"

	if got, want := countFailures(allResponses), 0; got != want {
		t.Errorf("%v: Wrong number of failures; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
	if countedNotes := countNotes(allResponses); returnedNotesCount != countedNotes {
		t.Errorf("%v: Inconsistent note count: returned %v, counted %v (proto data: %v", testName, returnedNotesCount, countedNotes, allResponses)
	}
	if got, want := returnedNotesCount, 10; got != want {
		t.Errorf("%v: Wrong number of notes; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
	if got, want := countCategoryNotes(allResponses, "JSHint"), 8; got != want {
		t.Errorf("%v: Wrong number of JSHint notes; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
	if got, want := countCategoryNotes(allResponses, "PostMessage"), 2; got != want {
		t.Errorf("%v: Wrong number of PostMessage notes; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
}

func TestExternalAnalyzers(t *testing.T) {
	// Replaces part of the e2e test
	// Create a fake maven project with android failures

	// Run CLI using a .shipshape file
}

func TestBuiltInAnalyzersPreBuild(t *testing.T) {
	// Replaces part of the e2e test
	// Test PostMessage and Go, with no kythe build

}

func TestBuiltInAnalyzersPostBuild(t *testing.T) {
	// Replaces part of the e2e test
	// Test with a kythe maven build
	// PostMessage and ErrorProne
}

func TestStreamsMode(t *testing.T) {
	// Test whether it works in streams mode
	// Before creating this, ensure that streams mode
	// is actually still something we need to support.
}

func TestChangingDirectories(t *testing.T) {
	// Replaces the changedir test
	// Make sure to test changing down, changing up, running on the same directory, running on a single file in the same directory, and changing to a sibling
}

func dumpLogs() {

}

func checkOutput(category string, numResults int) {

}
<commit_msg>Adds initial test configuration<commit_after>package cli

import (
	"flag"
	"testing"

	rpcpb "github.com/google/shipshape/shipshape/proto/shipshape_rpc_proto"
)

var dockerTag = flag.String("shipshape_test_docker_tag", "", "the docker tag for the images to use for testing")

func countFailures(resp rpcpb.ShipshapeResponse) int {
	failures := 0
	for _, analyzeResp := range resp.AnalyzeResponse {
		failures += len(analyzeResp.Failure)
	}
	return failures
}

func countNotes(resp rpcpb.ShipshapeResponse) int {
	notes := 0
	for _, analyzeResp := range resp.AnalyzeResponse {
		notes += len(analyzeResp.Note)
	}
	return notes
}

func countCategoryNotes(resp rpcpb.ShipshapeResponse, category string) int {
	notes := 0
	for _, analyzeResp := range resp.AnalyzeResponse {
		for _, note := range analyzeResp.Note {
			if *note.Category == category {
				notes += 1
			}
		}
	}
	return notes
}

func TestBasic(t *testing.T) {
	options := Options{
		File:                "shipshape/cli/testdata/workspace1",
		ThirdPartyAnalyzers: []string{},
		Build:               "maven",
		TriggerCats:         []string{"PostMessage", "JSHint"},
		Dind:                false,
		Event:               "manual", // TODO: const
		Repo:                "gcr.io/shipshape_releases", // TODO: const
		StayUp:              true,
		Tag:                 *dockerTag,
		// TODO(rsk): current e2e test can be ru both with & without kythe.
		LocalKythe:          false,
	}
	var allResponses rpcpb.ShipshapeResponse
	options.HandleResponse = func(shipshapeResp *rpcpb.ShipshapeResponse, _ string) error {
		allResponses.AnalyzeResponse = append(allResponses.AnalyzeResponse, shipshapeResp.AnalyzeResponse...)
		return nil
	}

	returnedNotesCount, err := New(options).Run()
	if err != nil {
		t.Fatal(err)
	}
	testName := "TestBasic"

	if got, want := countFailures(allResponses), 0; got != want {
		t.Errorf("%v: Wrong number of failures; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
	if countedNotes := countNotes(allResponses); returnedNotesCount != countedNotes {
		t.Errorf("%v: Inconsistent note count: returned %v, counted %v (proto data: %v", testName, returnedNotesCount, countedNotes, allResponses)
	}
	if got, want := returnedNotesCount, 10; got != want {
		t.Errorf("%v: Wrong number of notes; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
	if got, want := countCategoryNotes(allResponses, "JSHint"), 8; got != want {
		t.Errorf("%v: Wrong number of JSHint notes; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
	if got, want := countCategoryNotes(allResponses, "PostMessage"), 2; got != want {
		t.Errorf("%v: Wrong number of PostMessage notes; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
}

func TestExternalAnalyzers(t *testing.T) {
	// Replaces part of the e2e test
	// Create a fake maven project with android failures

	// Run CLI using a .shipshape file
}

func TestBuiltInAnalyzersPreBuild(t *testing.T) {
	// Replaces part of the e2e test
	// Test PostMessage and Go, with no kythe build
	options := Options{
		File:                "shipshape/cli/testdata/workspace1",
		ThirdPartyAnalyzers: []string{},
		Build:               "",
		TriggerCats:         []string{"PostMessage", "JSHint", "go vet", "PyLint"},
		Dind:                false,
		Event:               "manual", // TODO: const
		Repo:                "gcr.io/shipshape_releases", // TODO: const
		StayUp:              true,
		// TODO(rsk): current e2e test require tag to be specified on
		// the command line. Furthermore, if the tag is "local" it builds
		// containers and tags them.
		Tag:                 "prod",
		// TODO(rsk): current e2e test can be ru both with & without kythe.
		LocalKythe:          false,
	}
	var allResponses rpcpb.ShipshapeResponse
	options.HandleResponse = func(shipshapeResp *rpcpb.ShipshapeResponse, _ string) error {
		allResponses.AnalyzeResponse = append(allResponses.AnalyzeResponse, shipshapeResp.AnalyzeResponse...)
		return nil
	}
	returnedNotesCount, err := New(options).Run()
	if err != nil {
		t.Fatal(err)
	}
	testName := "TestBuiltInAnalyzerPreBuild"

	if got, want := countFailures(allResponses), 0; got != want {
		t.Errorf("%v: Wrong number of failures; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
	if countedNotes := countNotes(allResponses); returnedNotesCount != countedNotes {
		t.Errorf("%v: Inconsistent note count: returned %v, counted %v (proto data: %v", testName, returnedNotesCount, countedNotes, allResponses)
	}
	if got, want := returnedNotesCount, 10; got != want {
		t.Errorf("%v: Wrong number of notes; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
	if got, want := countCategoryNotes(allResponses, "PostMessage"), 2; got != want {
		t.Errorf("%v: Wrong number of PostMessage notes; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
	if got, want := countCategoryNotes(allResponses, "JSHint"), 8; got != want {
		t.Errorf("%v: Wrong number of JSHint notes; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
	if got, want := countCategoryNotes(allResponses, "go vet"), 0; got != want {
		t.Errorf("%v: Wrong number of PostMessage notes; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
	if got, want := countCategoryNotes(allResponses, "PyLint"), 0; got != want {
		t.Errorf("%v: Wrong number of PostMessage notes; got %v, want %v (proto data: %v)", testName, got, want, allResponses)
	}
}

func TestBuiltInAnalyzersPostBuild(t *testing.T) {
	// Replaces part of the e2e test
	// Test with a kythe maven build
	// PostMessage and ErrorProne
}

func TestStreamsMode(t *testing.T) {
	// Test whether it works in streams mode
	// Before creating this, ensure that streams mode
	// is actually still something we need to support.
}

func TestChangingDirectories(t *testing.T) {
	// Replaces the changedir test
	// Make sure to test changing down, changing up, running on the same directory, running on a single file in the same directory, and changing to a sibling
}

func dumpLogs() {

}

func checkOutput(category string, numResults int) {

}
<|endoftext|>


<commit_before>package api

import (
	"github.com/flexiant/concerto/testdata"

	"testing"
)

func TestGetTemplateList(t *testing.T) {
	templatesIn := testdata.GetTemplateData()
	GetTemplateListMocked(t, templatesIn)
}

func TestGetTemplate(t *testing.T) {
	templatesIn := testdata.GetTemplateData()
	for _, templateIn := range *templatesIn {
		GetTemplateMocked(t, &templateIn)
	}
}

func TestCreateTemplate(t *testing.T) {
	templatesIn := testdata.GetTemplateData()
	for _, templateIn := range *templatesIn {
		CreateTemplateMocked(t, &templateIn)
	}
}

func TestUpdateTemplate(t *testing.T) {
	templatesIn := testdata.GetTemplateData()
	for _, templateIn := range *templatesIn {
		UpdateTemplateMocked(t, &templateIn)
	}
}

func TestDeleteTemplate(t *testing.T) {
	templatesIn := testdata.GetTemplateData()
	for _, templateIn := range *templatesIn {
		DeleteTemplateMocked(t, &templateIn)
	}
}

func TestListTemplateScripts(t *testing.T) {
	drsIn := testdata.GetTemplateScriptData()
	for _, drIn := range *drsIn {
		GetTemplateScriptListMocked(t, drsIn, drIn.ID, drIn.Type)
	}
}

func TestGetTemplateScript(t *testing.T) {
	drsIn := testdata.GetTemplateScriptData()
	for _, drIn := range *drsIn {
		GetTemplateScriptMocked(t, &drIn)
	}
}

func TestCreateTemplateScript(t *testing.T) {
	drsIn := testdata.GetTemplateScriptData()
	for _, drIn := range *drsIn {
		CreateTemplateScriptMocked(t, &drIn)
	}
}

func TestUpdateTemplateScript(t *testing.T) {
	drsIn := testdata.GetTemplateScriptData()
	for _, drIn := range *drsIn {
		UpdateTemplateScriptMocked(t, &drIn)
	}
}

func TestDeleteTemplateScripts(t *testing.T) {
	drsIn := testdata.GetTemplateScriptData()
	for _, drIn := range *drsIn {
		DeleteTemplateScriptMocked(t, &drIn)
	}
}

func TestListTemplateServers(t *testing.T) {
	drsIn := testdata.GetTemplateServerData()
	for _, drIn := range *drsIn {
		GetTemplateServerListMocked(t, drsIn, drIn.ID)
	}
}
<commit_msg>yet another nil initialization service<commit_after>package api

import (
	"github.com/flexiant/concerto/testdata"
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestNewTemplateServiceNil(t *testing.T) {
	assert := assert.New(t)
	rs, err := NewTemplateService(nil)
	assert.Nil(rs, "Uninitialized service should return nil")
	assert.NotNil(err, "Uninitialized service should return error")
}

func TestGetTemplateList(t *testing.T) {
	templatesIn := testdata.GetTemplateData()
	GetTemplateListMocked(t, templatesIn)
}

func TestGetTemplate(t *testing.T) {
	templatesIn := testdata.GetTemplateData()
	for _, templateIn := range *templatesIn {
		GetTemplateMocked(t, &templateIn)
	}
}

func TestCreateTemplate(t *testing.T) {
	templatesIn := testdata.GetTemplateData()
	for _, templateIn := range *templatesIn {
		CreateTemplateMocked(t, &templateIn)
	}
}

func TestUpdateTemplate(t *testing.T) {
	templatesIn := testdata.GetTemplateData()
	for _, templateIn := range *templatesIn {
		UpdateTemplateMocked(t, &templateIn)
	}
}

func TestDeleteTemplate(t *testing.T) {
	templatesIn := testdata.GetTemplateData()
	for _, templateIn := range *templatesIn {
		DeleteTemplateMocked(t, &templateIn)
	}
}

func TestListTemplateScripts(t *testing.T) {
	drsIn := testdata.GetTemplateScriptData()
	for _, drIn := range *drsIn {
		GetTemplateScriptListMocked(t, drsIn, drIn.ID, drIn.Type)
	}
}

func TestGetTemplateScript(t *testing.T) {
	drsIn := testdata.GetTemplateScriptData()
	for _, drIn := range *drsIn {
		GetTemplateScriptMocked(t, &drIn)
	}
}

func TestCreateTemplateScript(t *testing.T) {
	drsIn := testdata.GetTemplateScriptData()
	for _, drIn := range *drsIn {
		CreateTemplateScriptMocked(t, &drIn)
	}
}

func TestUpdateTemplateScript(t *testing.T) {
	drsIn := testdata.GetTemplateScriptData()
	for _, drIn := range *drsIn {
		UpdateTemplateScriptMocked(t, &drIn)
	}
}

func TestDeleteTemplateScripts(t *testing.T) {
	drsIn := testdata.GetTemplateScriptData()
	for _, drIn := range *drsIn {
		DeleteTemplateScriptMocked(t, &drIn)
	}
}

func TestListTemplateServers(t *testing.T) {
	drsIn := testdata.GetTemplateServerData()
	for _, drIn := range *drsIn {
		GetTemplateServerListMocked(t, drsIn, drIn.ID)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"

	kapi "k8s.io/kubernetes/pkg/api"
	kcache "k8s.io/kubernetes/pkg/client/cache"
	kclient "k8s.io/kubernetes/pkg/client/unversioned"
	kframework "k8s.io/kubernetes/pkg/controller/framework"
	kselector "k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/util/wait"

	consulapi "github.com/hashicorp/consul/api"
)

type kube2consul struct {
	kubeClient    *kclient.Client
	consulClient  *consulapi.Client
	consulCatalog *consulapi.Catalog
}

func newKube2Consul(kc *kclient.Client, cc *consulapi.Client) *kube2consul {
	k2c := &kube2consul{
		kubeClient:    kc,
		consulClient:  cc,
		consulCatalog: cc.Catalog(),
	}
	return k2c
}

// watchForServices starts watching for new, removed or updated kubernetes services
func (kc *kube2consul) watchForServices() kcache.Store {
	serviceStore, serviceController := kframework.NewInformer(
		kcache.NewListWatchFromClient(kc.kubeClient, "services", kapi.NamespaceAll, kselector.Everything()),
		&kapi.Service{},
		resyncPeriod,
		kframework.ResourceEventHandlerFuncs{
			AddFunc:    kc.newService,
			DeleteFunc: kc.removeService,
			UpdateFunc: kc.updateService,
		},
	)
	go serviceController.Run(wait.NeverStop)
	return serviceStore
}

// newService registers a new kubernetes service in Consul
func (kc *kube2consul) newService(obj interface{}) {
	if s, ok := obj.(*kapi.Service); ok {
		log.Printf("Add Service %+v\n", s.GetName())
		service := &consulapi.AgentService{
			Service: s.GetName(),
			Tags:    []string{"kubernetes"},
		}
		if len(s.Spec.Ports) > 0 {
			service.Port = int(s.Spec.Ports[0].Port)
		}
		reg := &consulapi.CatalogRegistration{
			Node:    s.Namespace,
			Address: s.Spec.ClusterIP,
			Service: service,
			// Check: &consulapi.AgentCheck{
			// 	ServiceName: s.GetName(),
			// 	Name:        s.GetName() + " health check.",
			// 	Status:      "unknown",
			// },
		}
		wm, err := kc.consulCatalog.Register(reg, &consulapi.WriteOptions{})
		if err != nil {
			log.Println("Error registering service:", err)
		} else {
			log.Println(wm)
		}
	}
}

// removeService deregisters a kubernetes service in Consul
func (kc *kube2consul) removeService(obj interface{}) {
	if s, ok := obj.(*kapi.Service); ok {
		log.Printf("Remove Service %+v\n", s.GetName())
		service := &consulapi.CatalogDeregistration{
			ServiceID: s.GetName(),
		}
		_, err := kc.consulCatalog.Deregister(service, &consulapi.WriteOptions{})
		if err != nil {
			log.Println("Error registering service:", err)
		}
	}
}

func (kc *kube2consul) updateService(oldObj, obj interface{}) {
	kc.removeService(oldObj)
	kc.newService(obj)
}
<commit_msg>Added missing `Node` field in deregistration<commit_after>package main

import (
	"log"

	kapi "k8s.io/kubernetes/pkg/api"
	kcache "k8s.io/kubernetes/pkg/client/cache"
	kclient "k8s.io/kubernetes/pkg/client/unversioned"
	kframework "k8s.io/kubernetes/pkg/controller/framework"
	kselector "k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/util/wait"

	consulapi "github.com/hashicorp/consul/api"
)

type kube2consul struct {
	kubeClient    *kclient.Client
	consulClient  *consulapi.Client
	consulCatalog *consulapi.Catalog
}

func newKube2Consul(kc *kclient.Client, cc *consulapi.Client) *kube2consul {
	k2c := &kube2consul{
		kubeClient:    kc,
		consulClient:  cc,
		consulCatalog: cc.Catalog(),
	}
	return k2c
}

// watchForServices starts watching for new, removed or updated kubernetes services
func (kc *kube2consul) watchForServices() kcache.Store {
	serviceStore, serviceController := kframework.NewInformer(
		kcache.NewListWatchFromClient(kc.kubeClient, "services", kapi.NamespaceAll, kselector.Everything()),
		&kapi.Service{},
		resyncPeriod,
		kframework.ResourceEventHandlerFuncs{
			AddFunc:    kc.newService,
			DeleteFunc: kc.removeService,
			UpdateFunc: kc.updateService,
		},
	)
	go serviceController.Run(wait.NeverStop)
	return serviceStore
}

// newService registers a new kubernetes service in Consul
func (kc *kube2consul) newService(obj interface{}) {
	if s, ok := obj.(*kapi.Service); ok {
		log.Printf("Add Service %+v\n", s.GetName())
		service := &consulapi.AgentService{
			Service: s.GetName(),
			Tags:    []string{"kubernetes"},
		}
		if len(s.Spec.Ports) > 0 {
			service.Port = int(s.Spec.Ports[0].Port)
		}
		reg := &consulapi.CatalogRegistration{
			Node:    s.Namespace,
			Address: s.Spec.ClusterIP,
			Service: service,
			// Check: &consulapi.AgentCheck{
			// 	ServiceName: s.GetName(),
			// 	Name:        s.GetName() + " health check.",
			// 	Status:      "unknown",
			// },
		}
		wm, err := kc.consulCatalog.Register(reg, &consulapi.WriteOptions{})
		if err != nil {
			log.Println("Error registering service:", err)
		} else {
			log.Println(wm)
		}
	}
}

// removeService deregisters a kubernetes service in Consul
func (kc *kube2consul) removeService(obj interface{}) {
	if s, ok := obj.(*kapi.Service); ok {
		log.Printf("Remove Service %+v\n", s.GetName())
		service := &consulapi.CatalogDeregistration{
			ServiceID: s.GetName(),
			Node:      s.Namespace,
		}
		_, err := kc.consulCatalog.Deregister(service, &consulapi.WriteOptions{})
		if err != nil {
			log.Println("Error deregistering service:", err)
		}
	}
}

func (kc *kube2consul) updateService(oldObj, obj interface{}) {
	kc.removeService(oldObj)
	kc.newService(obj)
}
<|endoftext|>


<commit_before>// Copyright (c) 2018-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package blockchain

import (
	"bytes"
	"math"
	"sort"

	"github.com/decred/dcrd/chaincfg/chainhash"
)

// nodeHeightSorter implements sort.Interface to allow a slice of nodes to
// be sorted by height in ascending order.
type nodeHeightSorter []*blockNode

// Len returns the number of nodes in the slice.  It is part of the
// sort.Interface implementation.
func (s nodeHeightSorter) Len() int {
	return len(s)
}

// Swap swaps the nodes at the passed indices.  It is part of the
// sort.Interface implementation.
func (s nodeHeightSorter) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

// Less returns whether the node with index i should sort before the node with
// index j.  It is part of the sort.Interface implementation.
func (s nodeHeightSorter) Less(i, j int) bool {
	// To ensure stable order when the heights are the same, fall back to
	// sorting based on hash.
	if s[i].height == s[j].height {
		return bytes.Compare(s[i].hash[:], s[j].hash[:]) < 0
	}
	return s[i].height < s[j].height
}

// ChainTipInfo models information about a chain tip.
type ChainTipInfo struct {
	// Height specifies the block height of the chain tip.
	Height int64

	// Hash specifies the block hash of the chain tip.
	Hash chainhash.Hash

	// BranchLen specifies the length of the branch that connects the chain tip
	// to the main chain.  It will be zero for the main chain tip.
	BranchLen int64

	// Status specifies the validation status of chain formed by the chain tip.
	//
	// active:
	//   The current best chain tip.
	//
	// invalid:
	//   The block or one of its ancestors is invalid.
	//
	// headers-only:
	//   The block or one of its ancestors does not have the full block data
	//   available which also means the block can't be validated or connected.
	//
	// valid-fork:
	//   The block is fully validated which implies it was probably part of the
	//   main chain at one point and was reorganized.
	//
	// valid-headers:
	//   The full block data is available and the header is valid, but the block
	//   was never validated which implies it was probably never part of the
	//   main chain.
	Status string
}

// ChainTips returns information, in JSON-RPC format, about all of the currently
// known chain tips in the block index.
func (b *BlockChain) ChainTips() []ChainTipInfo {
	b.index.RLock()
	chainTips := make([]*blockNode, 0, b.index.totalTips)
	for _, entry := range b.index.chainTips {
		chainTips = append(chainTips, entry.tip)
		if len(entry.otherTips) > 0 {
			chainTips = append(chainTips, entry.otherTips...)
		}
	}
	b.index.RUnlock()

	// Generate the results sorted by descending height.
	sort.Sort(sort.Reverse(nodeHeightSorter(chainTips)))
	results := make([]ChainTipInfo, len(chainTips))
	bestTip := b.bestChain.Tip()
	for i, tip := range chainTips {
		result := &results[i]
		result.Height = tip.height
		result.Hash = tip.hash
		result.BranchLen = tip.height - b.bestChain.FindFork(tip).height

		// Determine the status of the chain tip.
		//
		// active:
		//   The current best chain tip.
		//
		// invalid:
		//   The block or one of its ancestors is invalid.
		//
		// headers-only:
		//   The block or one of its ancestors does not have the full block data
		//   available which also means the block can't be validated or
		//   connected.
		//
		// valid-fork:
		//   The block is fully validated which implies it was probably part of
		//   main chain at one point and was reorganized.
		//
		// valid-headers:
		//   The full block data is available and the header is valid, but the
		//   block was never validated which implies it was probably never part
		//   of the main chain.
		tipStatus := b.index.NodeStatus(tip)
		if tip == bestTip {
			result.Status = "active"
		} else if tipStatus.KnownInvalid() {
			result.Status = "invalid"
		} else if !tipStatus.HaveData() {
			result.Status = "headers-only"
		} else if tipStatus.HasValidated() {
			result.Status = "valid-fork"
		} else {
			result.Status = "valid-headers"
		}
	}
	return results
}

// BestHeader returns the header with the most cumulative work that is NOT
// known to be invalid.
func (b *BlockChain) BestHeader() (chainhash.Hash, int64) {
	b.index.RLock()
	header := b.index.bestHeader
	b.index.RUnlock()
	return header.hash, header.height
}

// BestInvalidHeader returns the header with the most cumulative work that is
// known to be invalid.  It will be a hash of all zeroes if there is no such
// header.
func (b *BlockChain) BestInvalidHeader() chainhash.Hash {
	var hash chainhash.Hash
	b.index.RLock()
	if b.index.bestInvalid != nil {
		hash = b.index.bestInvalid.hash
	}
	b.index.RUnlock()
	return hash
}

// PutNextNeededBlocks populates the provided slice with hashes for the next
// blocks after the current best chain tip that are needed to make progress
// towards the current best known header skipping any blocks that already have
// their data available.
//
// The provided slice will be populated with either as many hashes as it will
// fit per its length or as many hashes it takes to reach best header, whichever
// is smaller.
//
// It returns a sub slice of the provided one with its bounds adjusted to the
// number of entries populated.
//
// This function is safe for concurrent access.
func (b *BlockChain) PutNextNeededBlocks(out []chainhash.Hash) []chainhash.Hash {
	// Nothing to do when no results are requested.
	maxResults := len(out)
	if maxResults == 0 {
		return out[:0]
	}

	b.index.RLock()
	defer b.index.RUnlock()

	// Populate the provided slice by making use of a sliding window.  Note that
	// the needed block hashes are populated in forwards order while it is
	// necessary to walk the block index backwards to determine them.  Further,
	// an unknown number of blocks may already have their data and need to be
	// skipped, so it's not possible to determine the precise height after the
	// fork point to start iterating from.  Using a sliding window efficiently
	// handles these conditions without needing additional allocations.
	//
	// The strategy is to initially determine the common ancestor between the
	// current best chain tip and the current best known header as the starting
	// fork point and move the fork point forward by the window size after
	// populating the output slice with all relevant nodes in the window until
	// either there are no more results or the desired number of results have
	// been populated.
	const windowSize = 32
	var outputIdx int
	var window [windowSize]chainhash.Hash
	bestHeader := b.index.bestHeader
	fork := b.bestChain.FindFork(bestHeader)
	for outputIdx < maxResults && fork != nil && fork != bestHeader {
		// Determine the final descendant block on the branch that leads to the
		// best known header in this window by clamping the number of
		// descendants to consider to the window size.
		endNode := bestHeader
		numBlocksToConsider := endNode.height - fork.height
		if numBlocksToConsider > windowSize {
			endNode = endNode.Ancestor(fork.height + windowSize)
		}

		// Populate the blocks in this window from back to front by walking
		// backwards from the final block to consider in the window to the first
		// one excluding any blocks that already have their data available.
		windowIdx := windowSize
		for node := endNode; node != nil && node != fork; node = node.parent {
			if node.status.HaveData() {
				continue
			}

			windowIdx--
			window[windowIdx] = node.hash
		}

		// Populate the outputs with as many from the back of the window as
		// possible (since the window might not have been fully populated due to
		// skipped blocks) and move the output index forward to match.
		outputIdx += copy(out[outputIdx:], window[windowIdx:])

		// Move the fork point forward to the final block of the window.
		fork = endNode
	}

	return out[:outputIdx]
}

// VerifyProgress returns a percentage that is a guess of the progress of the
// chain verification process.
//
// This function is safe for concurrent access.
func (b *BlockChain) VerifyProgress() float64 {
	b.index.RLock()
	bestHdr := b.index.bestHeader
	b.index.RUnlock()
	if bestHdr.height == 0 {
		return 0.0
	}

	tip := b.bestChain.Tip()
	return math.Min(float64(tip.height)/float64(bestHdr.height), 1.0) * 100
}

// IsKnownInvalidBlock returns whether either the provided block is itself known
// to be invalid or to have an invalid ancestor.  A return value of false in no
// way implies the block is valid or only has valid ancestors.  Thus, this will
// return false for invalid blocks that have not been proven invalid yet as well
// as return false for blocks with invalid ancestors that have not been proven
// invalid yet.
//
// It will also return false when the provided block is unknown.
//
// This function is safe for concurrent access.
func (b *BlockChain) IsKnownInvalidBlock(hash *chainhash.Hash) bool {
	node := b.index.LookupNode(hash)
	if node == nil {
		return false
	}

	return b.index.NodeStatus(node).KnownInvalid()
}
<commit_msg>blockchain: Comment concurrency semantics.<commit_after>// Copyright (c) 2018-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package blockchain

import (
	"bytes"
	"math"
	"sort"

	"github.com/decred/dcrd/chaincfg/chainhash"
)

// nodeHeightSorter implements sort.Interface to allow a slice of nodes to
// be sorted by height in ascending order.
type nodeHeightSorter []*blockNode

// Len returns the number of nodes in the slice.  It is part of the
// sort.Interface implementation.
func (s nodeHeightSorter) Len() int {
	return len(s)
}

// Swap swaps the nodes at the passed indices.  It is part of the
// sort.Interface implementation.
func (s nodeHeightSorter) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

// Less returns whether the node with index i should sort before the node with
// index j.  It is part of the sort.Interface implementation.
func (s nodeHeightSorter) Less(i, j int) bool {
	// To ensure stable order when the heights are the same, fall back to
	// sorting based on hash.
	if s[i].height == s[j].height {
		return bytes.Compare(s[i].hash[:], s[j].hash[:]) < 0
	}
	return s[i].height < s[j].height
}

// ChainTipInfo models information about a chain tip.
type ChainTipInfo struct {
	// Height specifies the block height of the chain tip.
	Height int64

	// Hash specifies the block hash of the chain tip.
	Hash chainhash.Hash

	// BranchLen specifies the length of the branch that connects the chain tip
	// to the main chain.  It will be zero for the main chain tip.
	BranchLen int64

	// Status specifies the validation status of chain formed by the chain tip.
	//
	// active:
	//   The current best chain tip.
	//
	// invalid:
	//   The block or one of its ancestors is invalid.
	//
	// headers-only:
	//   The block or one of its ancestors does not have the full block data
	//   available which also means the block can't be validated or connected.
	//
	// valid-fork:
	//   The block is fully validated which implies it was probably part of the
	//   main chain at one point and was reorganized.
	//
	// valid-headers:
	//   The full block data is available and the header is valid, but the block
	//   was never validated which implies it was probably never part of the
	//   main chain.
	Status string
}

// ChainTips returns information, in JSON-RPC format, about all of the currently
// known chain tips in the block index.
//
// This function is safe for concurrent access.
func (b *BlockChain) ChainTips() []ChainTipInfo {
	b.index.RLock()
	chainTips := make([]*blockNode, 0, b.index.totalTips)
	for _, entry := range b.index.chainTips {
		chainTips = append(chainTips, entry.tip)
		if len(entry.otherTips) > 0 {
			chainTips = append(chainTips, entry.otherTips...)
		}
	}
	b.index.RUnlock()

	// Generate the results sorted by descending height.
	sort.Sort(sort.Reverse(nodeHeightSorter(chainTips)))
	results := make([]ChainTipInfo, len(chainTips))
	bestTip := b.bestChain.Tip()
	for i, tip := range chainTips {
		result := &results[i]
		result.Height = tip.height
		result.Hash = tip.hash
		result.BranchLen = tip.height - b.bestChain.FindFork(tip).height

		// Determine the status of the chain tip.
		//
		// active:
		//   The current best chain tip.
		//
		// invalid:
		//   The block or one of its ancestors is invalid.
		//
		// headers-only:
		//   The block or one of its ancestors does not have the full block data
		//   available which also means the block can't be validated or
		//   connected.
		//
		// valid-fork:
		//   The block is fully validated which implies it was probably part of
		//   main chain at one point and was reorganized.
		//
		// valid-headers:
		//   The full block data is available and the header is valid, but the
		//   block was never validated which implies it was probably never part
		//   of the main chain.
		tipStatus := b.index.NodeStatus(tip)
		if tip == bestTip {
			result.Status = "active"
		} else if tipStatus.KnownInvalid() {
			result.Status = "invalid"
		} else if !tipStatus.HaveData() {
			result.Status = "headers-only"
		} else if tipStatus.HasValidated() {
			result.Status = "valid-fork"
		} else {
			result.Status = "valid-headers"
		}
	}
	return results
}

// BestHeader returns the header with the most cumulative work that is NOT
// known to be invalid.
//
// This function is safe for concurrent access.
func (b *BlockChain) BestHeader() (chainhash.Hash, int64) {
	b.index.RLock()
	header := b.index.bestHeader
	b.index.RUnlock()
	return header.hash, header.height
}

// BestInvalidHeader returns the header with the most cumulative work that is
// known to be invalid.  It will be a hash of all zeroes if there is no such
// header.
//
// This function is safe for concurrent access.
func (b *BlockChain) BestInvalidHeader() chainhash.Hash {
	var hash chainhash.Hash
	b.index.RLock()
	if b.index.bestInvalid != nil {
		hash = b.index.bestInvalid.hash
	}
	b.index.RUnlock()
	return hash
}

// PutNextNeededBlocks populates the provided slice with hashes for the next
// blocks after the current best chain tip that are needed to make progress
// towards the current best known header skipping any blocks that already have
// their data available.
//
// The provided slice will be populated with either as many hashes as it will
// fit per its length or as many hashes it takes to reach best header, whichever
// is smaller.
//
// It returns a sub slice of the provided one with its bounds adjusted to the
// number of entries populated.
//
// This function is safe for concurrent access.
func (b *BlockChain) PutNextNeededBlocks(out []chainhash.Hash) []chainhash.Hash {
	// Nothing to do when no results are requested.
	maxResults := len(out)
	if maxResults == 0 {
		return out[:0]
	}

	b.index.RLock()
	defer b.index.RUnlock()

	// Populate the provided slice by making use of a sliding window.  Note that
	// the needed block hashes are populated in forwards order while it is
	// necessary to walk the block index backwards to determine them.  Further,
	// an unknown number of blocks may already have their data and need to be
	// skipped, so it's not possible to determine the precise height after the
	// fork point to start iterating from.  Using a sliding window efficiently
	// handles these conditions without needing additional allocations.
	//
	// The strategy is to initially determine the common ancestor between the
	// current best chain tip and the current best known header as the starting
	// fork point and move the fork point forward by the window size after
	// populating the output slice with all relevant nodes in the window until
	// either there are no more results or the desired number of results have
	// been populated.
	const windowSize = 32
	var outputIdx int
	var window [windowSize]chainhash.Hash
	bestHeader := b.index.bestHeader
	fork := b.bestChain.FindFork(bestHeader)
	for outputIdx < maxResults && fork != nil && fork != bestHeader {
		// Determine the final descendant block on the branch that leads to the
		// best known header in this window by clamping the number of
		// descendants to consider to the window size.
		endNode := bestHeader
		numBlocksToConsider := endNode.height - fork.height
		if numBlocksToConsider > windowSize {
			endNode = endNode.Ancestor(fork.height + windowSize)
		}

		// Populate the blocks in this window from back to front by walking
		// backwards from the final block to consider in the window to the first
		// one excluding any blocks that already have their data available.
		windowIdx := windowSize
		for node := endNode; node != nil && node != fork; node = node.parent {
			if node.status.HaveData() {
				continue
			}

			windowIdx--
			window[windowIdx] = node.hash
		}

		// Populate the outputs with as many from the back of the window as
		// possible (since the window might not have been fully populated due to
		// skipped blocks) and move the output index forward to match.
		outputIdx += copy(out[outputIdx:], window[windowIdx:])

		// Move the fork point forward to the final block of the window.
		fork = endNode
	}

	return out[:outputIdx]
}

// VerifyProgress returns a percentage that is a guess of the progress of the
// chain verification process.
//
// This function is safe for concurrent access.
func (b *BlockChain) VerifyProgress() float64 {
	b.index.RLock()
	bestHdr := b.index.bestHeader
	b.index.RUnlock()
	if bestHdr.height == 0 {
		return 0.0
	}

	tip := b.bestChain.Tip()
	return math.Min(float64(tip.height)/float64(bestHdr.height), 1.0) * 100
}

// IsKnownInvalidBlock returns whether either the provided block is itself known
// to be invalid or to have an invalid ancestor.  A return value of false in no
// way implies the block is valid or only has valid ancestors.  Thus, this will
// return false for invalid blocks that have not been proven invalid yet as well
// as return false for blocks with invalid ancestors that have not been proven
// invalid yet.
//
// It will also return false when the provided block is unknown.
//
// This function is safe for concurrent access.
func (b *BlockChain) IsKnownInvalidBlock(hash *chainhash.Hash) bool {
	node := b.index.LookupNode(hash)
	if node == nil {
		return false
	}

	return b.index.NodeStatus(node).KnownInvalid()
}
<|endoftext|>


<commit_before>package database

import (
	"fmt"
	"strings"
	"time"

	"github.com/fatih/structs"
	"github.com/hashicorp/vault/builtin/logical/database/dbs"
	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/logical/framework"
)

func pathResetConnection(b *databaseBackend) *framework.Path {
	return &framework.Path{
		Pattern: fmt.Sprintf("reset/%s", framework.GenericNameRegex("name")),
		Fields: map[string]*framework.FieldSchema{
			"name": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "Name of this DB type",
			},
		},

		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.UpdateOperation: b.pathConnectionReset,
		},

		HelpSynopsis:    pathConfigConnectionHelpSyn,
		HelpDescription: pathConfigConnectionHelpDesc,
	}
}

func (b *databaseBackend) pathConnectionReset(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	name := data.Get("name").(string)
	if name == "" {
		return logical.ErrorResponse("Empty name attribute given"), nil
	}

	// Grab the mutex lock
	b.Lock()
	defer b.Unlock()

	db, ok := b.connections[name]
	if ok {
		db.Close()
		delete(b.connections, name)
	}

	db, err := b.getOrCreateDBObj(req.Storage, name)
	if err != nil {
		return nil, err
	}

	return nil, nil
}

// pathConfigureBuiltinConnection returns a configured framework.Path setup to
// operate on builtin databases.
func pathConfigureBuiltinConnection(b *databaseBackend) *framework.Path {
	return buildConfigConnectionPath("dbs/%s", b.connectionWriteHandler(dbs.BuiltinFactory), b.connectionReadHandler(), b.connectionDeleteHandler())
}

// pathConfigurePluginConnection returns a configured framework.Path setup to
// operate on plugins.
func pathConfigurePluginConnection(b *databaseBackend) *framework.Path {
	return buildConfigConnectionPath("dbs/plugin/%s", b.connectionWriteHandler(dbs.PluginFactory), b.connectionReadHandler(), b.connectionDeleteHandler())
}

// buildConfigConnectionPath reutns a configured framework.Path using the passed
// in operation functions to complete the request. Used to distinguish calls
// between builtin and plugin databases.
func buildConfigConnectionPath(path string, updateOp, readOp, deleteOp framework.OperationFunc) *framework.Path {
	return &framework.Path{
		Pattern: fmt.Sprintf(path, framework.GenericNameRegex("name")),
		Fields: map[string]*framework.FieldSchema{
			"name": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "Name of this DB type",
			},

			"connection_type": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "DB type (e.g. postgres)",
			},

			"verify_connection": &framework.FieldSchema{
				Type:        framework.TypeBool,
				Default:     true,
				Description: `If set, connection_url is verified by actually connecting to the database`,
			},

			"max_open_connections": &framework.FieldSchema{
				Type: framework.TypeInt,
				Description: `Maximum number of open connections to the database;
a zero uses the default value of two and a
negative value means unlimited`,
			},

			"max_idle_connections": &framework.FieldSchema{
				Type: framework.TypeInt,
				Description: `Maximum number of idle connections to the database;
a zero uses the value of max_open_connections
and a negative value disables idle connections.
If larger than max_open_connections it will be
reduced to the same size.`,
			},

			"max_connection_lifetime": &framework.FieldSchema{
				Type:    framework.TypeString,
				Default: "0s",
				Description: `Maximum amount of time a connection may be reused;
				a zero or negative value reuses connections forever.`,
			},

			"plugin_name": &framework.FieldSchema{
				Type: framework.TypeString,
				Description: `Maximum amount of time a connection may be reused;
				a zero or negative value reuses connections forever.`,
			},
		},

		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.UpdateOperation: updateOp,
			logical.ReadOperation:   readOp,
			logical.DeleteOperation: deleteOp,
		},

		HelpSynopsis:    pathConfigConnectionHelpSyn,
		HelpDescription: pathConfigConnectionHelpDesc,
	}
}

// pathConnectionRead reads out the connection configuration
func (b *databaseBackend) connectionReadHandler() framework.OperationFunc {
	return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
		name := data.Get("name").(string)

		entry, err := req.Storage.Get(fmt.Sprintf("dbs/%s", name))
		if err != nil {
			return nil, fmt.Errorf("failed to read connection configuration")
		}
		if entry == nil {
			return nil, nil
		}

		var config dbs.DatabaseConfig
		if err := entry.DecodeJSON(&config); err != nil {
			return nil, err
		}
		return &logical.Response{
			Data: structs.New(config).Map(),
		}, nil
	}
}

// connectionDeleteHandler deletes the connection configuration
func (b *databaseBackend) connectionDeleteHandler() framework.OperationFunc {
	return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
		name := data.Get("name").(string)
		if name == "" {
			return logical.ErrorResponse("Empty name attribute given"), nil
		}

		err := req.Storage.Delete(fmt.Sprintf("dbs/%s", name))
		if err != nil {
			return nil, fmt.Errorf("failed to delete connection configuration")
		}

		b.Lock()
		defer b.Unlock()

		if _, ok := b.connections[name]; ok {
			err = b.connections[name].Close()
			if err != nil {
				return nil, err
			}
		}

		delete(b.connections, name)

		return nil, nil
	}
}

// connectionWriteHandler returns a handler function for creating and updating
// both builtin and plugin database types.
func (b *databaseBackend) connectionWriteHandler(factory dbs.Factory) framework.OperationFunc {
	return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
		connType := data.Get("connection_type").(string)
		if connType == "" {
			return logical.ErrorResponse("connection_type not set"), nil
		}

		maxOpenConns := data.Get("max_open_connections").(int)
		if maxOpenConns == 0 {
			maxOpenConns = 2
		}

		maxIdleConns := data.Get("max_idle_connections").(int)
		if maxIdleConns == 0 {
			maxIdleConns = maxOpenConns
		}
		if maxIdleConns > maxOpenConns {
			maxIdleConns = maxOpenConns
		}

		maxConnLifetimeRaw := data.Get("max_connection_lifetime").(string)
		maxConnLifetime, err := time.ParseDuration(maxConnLifetimeRaw)
		if err != nil {
			return logical.ErrorResponse(fmt.Sprintf(
				"Invalid max_connection_lifetime: %s", err)), nil
		}

		config := &dbs.DatabaseConfig{
			DatabaseType:          connType,
			ConnectionDetails:     data.Raw,
			MaxOpenConnections:    maxOpenConns,
			MaxIdleConnections:    maxIdleConns,
			MaxConnectionLifetime: maxConnLifetime,
			PluginName:            data.Get("plugin_name").(string),
		}

		name := data.Get("name").(string)
		if name == "" {
			return logical.ErrorResponse("Empty name attribute given"), nil
		}

		verifyConnection := data.Get("verify_connection").(bool)

		// Grab the mutex lock
		b.Lock()
		defer b.Unlock()

		db, err := factory(config, b.System(), b.logger)
		if err != nil {
			return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil
		}

		err = db.Initialize(config.ConnectionDetails)
		if err != nil {
			if !strings.Contains(err.Error(), "Error Initializing Connection") {
				return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil
			}

			if verifyConnection {
				return logical.ErrorResponse(err.Error()), nil
			}
		}

		if _, ok := b.connections[name]; ok {
			newType := db.Type()

			// Don't update connection until the reset api is hit, close for
			// now.
			err = db.Close()
			if err != nil {
				return nil, err
			}

			// Don't allow the connection type to change
			if b.connections[name].Type() != newType {
				return logical.ErrorResponse("Can not change type of existing connection."), nil
			}
		} else {
			// Save the new connection
			b.connections[name] = db
		}

		// Store it
		entry, err := logical.StorageEntryJSON(fmt.Sprintf("dbs/%s", name), config)
		if err != nil {
			return nil, err
		}
		if err := req.Storage.Put(entry); err != nil {
			return nil, err
		}

		resp := &logical.Response{}
		resp.AddWarning("Read access to this endpoint should be controlled via ACLs as it will return the connection string or URL as it is, including passwords, if any.")

		return resp, nil
	}
}

const pathConfigConnectionHelpSyn = `
Configure the connection string to talk to PostgreSQL.
`

const pathConfigConnectionHelpDesc = `
This path configures the connection string used to connect to PostgreSQL.
The value of the string can be a URL, or a PG style string in the
format of "user=foo host=bar" etc.

The URL looks like:
"postgresql://user:pass@host:port/dbname"

When configuring the connection string, the backend will verify its validity.
`
<commit_msg>On change of configuration rotate the database type<commit_after>package database

import (
	"fmt"
	"strings"
	"time"

	"github.com/fatih/structs"
	"github.com/hashicorp/vault/builtin/logical/database/dbs"
	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/logical/framework"
)

func pathResetConnection(b *databaseBackend) *framework.Path {
	return &framework.Path{
		Pattern: fmt.Sprintf("reset/%s", framework.GenericNameRegex("name")),
		Fields: map[string]*framework.FieldSchema{
			"name": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "Name of this DB type",
			},
		},

		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.UpdateOperation: b.pathConnectionReset,
		},

		HelpSynopsis:    pathConfigConnectionHelpSyn,
		HelpDescription: pathConfigConnectionHelpDesc,
	}
}

func (b *databaseBackend) pathConnectionReset(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	name := data.Get("name").(string)
	if name == "" {
		return logical.ErrorResponse("Empty name attribute given"), nil
	}

	// Grab the mutex lock
	b.Lock()
	defer b.Unlock()

	db, ok := b.connections[name]
	if ok {
		db.Close()
		delete(b.connections, name)
	}

	db, err := b.getOrCreateDBObj(req.Storage, name)
	if err != nil {
		return nil, err
	}

	return nil, nil
}

// pathConfigureBuiltinConnection returns a configured framework.Path setup to
// operate on builtin databases.
func pathConfigureBuiltinConnection(b *databaseBackend) *framework.Path {
	return buildConfigConnectionPath("dbs/%s", b.connectionWriteHandler(dbs.BuiltinFactory), b.connectionReadHandler(), b.connectionDeleteHandler())
}

// pathConfigurePluginConnection returns a configured framework.Path setup to
// operate on plugins.
func pathConfigurePluginConnection(b *databaseBackend) *framework.Path {
	return buildConfigConnectionPath("dbs/plugin/%s", b.connectionWriteHandler(dbs.PluginFactory), b.connectionReadHandler(), b.connectionDeleteHandler())
}

// buildConfigConnectionPath reutns a configured framework.Path using the passed
// in operation functions to complete the request. Used to distinguish calls
// between builtin and plugin databases.
func buildConfigConnectionPath(path string, updateOp, readOp, deleteOp framework.OperationFunc) *framework.Path {
	return &framework.Path{
		Pattern: fmt.Sprintf(path, framework.GenericNameRegex("name")),
		Fields: map[string]*framework.FieldSchema{
			"name": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "Name of this DB type",
			},

			"connection_type": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "DB type (e.g. postgres)",
			},

			"verify_connection": &framework.FieldSchema{
				Type:        framework.TypeBool,
				Default:     true,
				Description: `If set, connection_url is verified by actually connecting to the database`,
			},

			"max_open_connections": &framework.FieldSchema{
				Type: framework.TypeInt,
				Description: `Maximum number of open connections to the database;
a zero uses the default value of two and a
negative value means unlimited`,
			},

			"max_idle_connections": &framework.FieldSchema{
				Type: framework.TypeInt,
				Description: `Maximum number of idle connections to the database;
a zero uses the value of max_open_connections
and a negative value disables idle connections.
If larger than max_open_connections it will be
reduced to the same size.`,
			},

			"max_connection_lifetime": &framework.FieldSchema{
				Type:    framework.TypeString,
				Default: "0s",
				Description: `Maximum amount of time a connection may be reused;
				a zero or negative value reuses connections forever.`,
			},

			"plugin_name": &framework.FieldSchema{
				Type: framework.TypeString,
				Description: `Maximum amount of time a connection may be reused;
				a zero or negative value reuses connections forever.`,
			},
		},

		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.UpdateOperation: updateOp,
			logical.ReadOperation:   readOp,
			logical.DeleteOperation: deleteOp,
		},

		HelpSynopsis:    pathConfigConnectionHelpSyn,
		HelpDescription: pathConfigConnectionHelpDesc,
	}
}

// pathConnectionRead reads out the connection configuration
func (b *databaseBackend) connectionReadHandler() framework.OperationFunc {
	return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
		name := data.Get("name").(string)

		entry, err := req.Storage.Get(fmt.Sprintf("dbs/%s", name))
		if err != nil {
			return nil, fmt.Errorf("failed to read connection configuration")
		}
		if entry == nil {
			return nil, nil
		}

		var config dbs.DatabaseConfig
		if err := entry.DecodeJSON(&config); err != nil {
			return nil, err
		}
		return &logical.Response{
			Data: structs.New(config).Map(),
		}, nil
	}
}

// connectionDeleteHandler deletes the connection configuration
func (b *databaseBackend) connectionDeleteHandler() framework.OperationFunc {
	return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
		name := data.Get("name").(string)
		if name == "" {
			return logical.ErrorResponse("Empty name attribute given"), nil
		}

		err := req.Storage.Delete(fmt.Sprintf("dbs/%s", name))
		if err != nil {
			return nil, fmt.Errorf("failed to delete connection configuration")
		}

		b.Lock()
		defer b.Unlock()

		if _, ok := b.connections[name]; ok {
			err = b.connections[name].Close()
			if err != nil {
				return nil, err
			}
		}

		delete(b.connections, name)

		return nil, nil
	}
}

// connectionWriteHandler returns a handler function for creating and updating
// both builtin and plugin database types.
func (b *databaseBackend) connectionWriteHandler(factory dbs.Factory) framework.OperationFunc {
	return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
		connType := data.Get("connection_type").(string)
		if connType == "" {
			return logical.ErrorResponse("connection_type not set"), nil
		}

		maxOpenConns := data.Get("max_open_connections").(int)
		if maxOpenConns == 0 {
			maxOpenConns = 2
		}

		maxIdleConns := data.Get("max_idle_connections").(int)
		if maxIdleConns == 0 {
			maxIdleConns = maxOpenConns
		}
		if maxIdleConns > maxOpenConns {
			maxIdleConns = maxOpenConns
		}

		maxConnLifetimeRaw := data.Get("max_connection_lifetime").(string)
		maxConnLifetime, err := time.ParseDuration(maxConnLifetimeRaw)
		if err != nil {
			return logical.ErrorResponse(fmt.Sprintf(
				"Invalid max_connection_lifetime: %s", err)), nil
		}

		config := &dbs.DatabaseConfig{
			DatabaseType:          connType,
			ConnectionDetails:     data.Raw,
			MaxOpenConnections:    maxOpenConns,
			MaxIdleConnections:    maxIdleConns,
			MaxConnectionLifetime: maxConnLifetime,
			PluginName:            data.Get("plugin_name").(string),
		}

		name := data.Get("name").(string)
		if name == "" {
			return logical.ErrorResponse("Empty name attribute given"), nil
		}

		verifyConnection := data.Get("verify_connection").(bool)

		// Grab the mutex lock
		b.Lock()
		defer b.Unlock()

		db, err := factory(config, b.System(), b.logger)
		if err != nil {
			return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil
		}

		err = db.Initialize(config.ConnectionDetails)
		if err != nil {
			if !strings.Contains(err.Error(), "Error Initializing Connection") {
				return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil
			}

			if verifyConnection {
				return logical.ErrorResponse("Could not verify connection"), nil
			}
		}

		if _, ok := b.connections[name]; ok {
			// Close and remove the old connection
			err := b.connections[name].Close()
			if err != nil {
				return nil, err
			}

			delete(b.connections, name)
		}

		// Save the new connection
		b.connections[name] = db

		// Store it
		entry, err := logical.StorageEntryJSON(fmt.Sprintf("dbs/%s", name), config)
		if err != nil {
			return nil, err
		}
		if err := req.Storage.Put(entry); err != nil {
			return nil, err
		}

		resp := &logical.Response{}
		resp.AddWarning("Read access to this endpoint should be controlled via ACLs as it will return the connection string or URL as it is, including passwords, if any.")

		return resp, nil
	}
}

const pathConfigConnectionHelpSyn = `
Configure the connection string to talk to PostgreSQL.
`

const pathConfigConnectionHelpDesc = `
This path configures the connection string used to connect to PostgreSQL.
The value of the string can be a URL, or a PG style string in the
format of "user=foo host=bar" etc.

The URL looks like:
"postgresql://user:pass@host:port/dbname"

When configuring the connection string, the backend will verify its validity.
`
<|endoftext|>