<commit_before>package command

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/hashicorp/nomad/api"
	"github.com/hashicorp/nomad/jobspec"
	"github.com/hashicorp/nomad/nomad/structs"
)

var (
	// enforceIndexRegex is a regular expression which extracts the enforcement error
	enforceIndexRegex = regexp.MustCompile(`\((Enforcing job modify index.*)\)`)
)

type RunCommand struct {
	Meta
}

func (c *RunCommand) Help() string {
	helpText := `
Usage: nomad run [options] <file>

  Starts running a new job or updates an existing job using
  the specification located at <file>. This is the main command
  used to interact with Nomad.

  Upon successful job submission, this command will immediately
  enter an interactive monitor. This is useful to watch Nomad's
  internals make scheduling decisions and place the submitted work
  onto nodes. The monitor will end once job placement is done. It
  is safe to exit the monitor early using ctrl+c.

  On successful job submission and scheduling, exit code 0 will be
  returned. If there are job placement issues encountered
  (unsatisfiable constraints, resource exhaustion, etc), then the
  exit code will be 2. Any other errors, including client connection
  issues or internal errors, are indicated by exit code 1.

  If the job has specified the region, the -region flag and NOMAD_REGION
  environment variable are overridden and the the job's region is used.

General Options:

  ` + generalOptionsUsage() + `

Run Options:

  -check-index
	If set, the job is only registered or updated if the the passed
	job modify index matches the server side version. If a check-index value of
	zero is passed, the job is only registered if it does not yet exist. If a
	non-zero value is passed, it ensures that the job is being updated from a
	known state. The use of this flag is most common in conjunction with plan
	command.

  -detach
	Return immediately instead of entering monitor mode. After job submission,
	the evaluation ID will be printed to the screen, which can be used to
	examine the evaluation using the eval-status command.

  -verbose
    Display full information.

  -output
    Output the JSON that would be submitted to the HTTP API without submitting
    the job.
`
	return strings.TrimSpace(helpText)
}

func (c *RunCommand) Synopsis() string {
	return "Run a new job or update an existing job"
}

func (c *RunCommand) Run(args []string) int {
	var detach, verbose, output bool
	var checkIndexStr string

	flags := c.Meta.FlagSet("run", FlagSetClient)
	flags.Usage = func() { c.Ui.Output(c.Help()) }
	flags.BoolVar(&detach, "detach", false, "")
	flags.BoolVar(&verbose, "verbose", false, "")
	flags.BoolVar(&output, "output", false, "")
	flags.StringVar(&checkIndexStr, "check-index", "", "")

	if err := flags.Parse(args); err != nil {
		return 1
	}

	// Truncate the id unless full length is requested
	length := shortId
	if verbose {
		length = fullId
	}

	// Check that we got exactly one node
	args = flags.Args()
	if len(args) != 1 {
		c.Ui.Error(c.Help())
		return 1
	}
	file := args[0]

	// Parse the job file
	job, err := jobspec.ParseFile(file)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error parsing job file %s: %s", file, err))
		return 1
	}

	// Initialize any fields that need to be.
	job.InitFields()

	// Check that the job is valid
	if err := job.Validate(); err != nil {
		c.Ui.Error(fmt.Sprintf("Error validating job: %s", err))
		return 1
	}

	// Check if the job is periodic.
	periodic := job.IsPeriodic()

	// Convert it to something we can use
	apiJob, err := convertStructJob(job)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error converting job: %s", err))
		return 1
	}

	if output {
		req := api.RegisterJobRequest{Job: apiJob}
		buf, err := json.MarshalIndent(req, "", "    ")
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error converting job: %s", err))
			return 1
		}

		c.Ui.Output(string(buf))
		return 0
	}

	// Get the HTTP client
	client, err := c.Meta.Client()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
		return 1
	}

	// Force the region to be that of the job.
	if r := job.Region; r != "" {
		client.SetRegion(r)
	}

	// Parse the check-index
	checkIndex, enforce, err := parseCheckIndex(checkIndexStr)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error parsing check-index value %q: %v", checkIndexStr, err))
		return 1
	}

	// Submit the job
	var evalID string
	if enforce {
		evalID, _, err = client.Jobs().EnforceRegister(apiJob, checkIndex, nil)
	} else {
		evalID, _, err = client.Jobs().Register(apiJob, nil)
	}
	if err != nil {
		if strings.Contains(err.Error(), api.RegisterEnforceIndexErrPrefix) {
			// Format the error specially if the error is due to index
			// enforcement
			matches := enforceIndexRegex.FindStringSubmatch(err.Error())
			if len(matches) == 2 {
				c.Ui.Error(matches[1]) // The matched group
				c.Ui.Error("Job not updated")
				return 1
			}
		}

		c.Ui.Error(fmt.Sprintf("Error submitting job: %s", err))
		return 1
	}

	// Check if we should enter monitor mode
	if detach || periodic {
		c.Ui.Output("Job registration successful")
		if periodic {
			now := time.Now().UTC()
			next := job.Periodic.Next(now)
			c.Ui.Output(fmt.Sprintf("Approximate next launch time: %s (%s from now)",
				formatTime(next), formatTimeDifference(now, next, time.Second)))
		} else {
			c.Ui.Output("Evaluation ID: " + evalID)
		}

		return 0
	}

	// Detach was not specified, so start monitoring
	mon := newMonitor(c.Ui, client, length)
	return mon.monitor(evalID, false)

}

// parseCheckIndex parses the check-index flag and returns the index, whether it
// was set and potentially an error during parsing.
func parseCheckIndex(input string) (uint64, bool, error) {
	if input == "" {
		return 0, false, nil
	}

	u, err := strconv.ParseUint(input, 10, 64)
	return u, true, err
}

// convertStructJob is used to take a *structs.Job and convert it to an *api.Job.
// This function is just a hammer and probably needs to be revisited.
func convertStructJob(in *structs.Job) (*api.Job, error) {
	gob.Register([]map[string]interface{}{})
	gob.Register([]interface{}{})
	var apiJob *api.Job
	buf := new(bytes.Buffer)
	if err := gob.NewEncoder(buf).Encode(in); err != nil {
		return nil, err
	}
	if err := gob.NewDecoder(buf).Decode(&apiJob); err != nil {
		return nil, err
	}
	return apiJob, nil
}
<commit_msg>Read Nomad Jobfile from STDIN if specified.<commit_after>package command

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/hashicorp/nomad/api"
	"github.com/hashicorp/nomad/jobspec"
	"github.com/hashicorp/nomad/nomad/structs"
)

var (
	// enforceIndexRegex is a regular expression which extracts the enforcement error
	enforceIndexRegex = regexp.MustCompile(`\((Enforcing job modify index.*)\)`)
)

type RunCommand struct {
	Meta
}

func (c *RunCommand) Help() string {
	helpText := `
Usage: nomad run [options] <path>

  Starts running a new job or updates an existing job using
  the specification located at <path>. This is the main command
  used to interact with Nomad.

  If the supplied path is "-", the jobfile is read from stdin. Otherwise
  it is read from the file at the supplied path.

  Upon successful job submission, this command will immediately
  enter an interactive monitor. This is useful to watch Nomad's
  internals make scheduling decisions and place the submitted work
  onto nodes. The monitor will end once job placement is done. It
  is safe to exit the monitor early using ctrl+c.

  On successful job submission and scheduling, exit code 0 will be
  returned. If there are job placement issues encountered
  (unsatisfiable constraints, resource exhaustion, etc), then the
  exit code will be 2. Any other errors, including client connection
  issues or internal errors, are indicated by exit code 1.

  If the job has specified the region, the -region flag and NOMAD_REGION
  environment variable are overridden and the the job's region is used.

General Options:

  ` + generalOptionsUsage() + `

Run Options:

  -check-index
	If set, the job is only registered or updated if the the passed
	job modify index matches the server side version. If a check-index value of
	zero is passed, the job is only registered if it does not yet exist. If a
	non-zero value is passed, it ensures that the job is being updated from a
	known state. The use of this flag is most common in conjunction with plan
	command.

  -detach
	Return immediately instead of entering monitor mode. After job submission,
	the evaluation ID will be printed to the screen, which can be used to
	examine the evaluation using the eval-status command.

  -verbose
    Display full information.

  -output
    Output the JSON that would be submitted to the HTTP API without submitting
    the job.
`
	return strings.TrimSpace(helpText)
}

func (c *RunCommand) Synopsis() string {
	return "Run a new job or update an existing job"
}

func (c *RunCommand) Run(args []string) int {
	var detach, verbose, output bool
	var checkIndexStr string

	flags := c.Meta.FlagSet("run", FlagSetClient)
	flags.Usage = func() { c.Ui.Output(c.Help()) }
	flags.BoolVar(&detach, "detach", false, "")
	flags.BoolVar(&verbose, "verbose", false, "")
	flags.BoolVar(&output, "output", false, "")
	flags.StringVar(&checkIndexStr, "check-index", "", "")

	if err := flags.Parse(args); err != nil {
		return 1
	}

	// Truncate the id unless full length is requested
	length := shortId
	if verbose {
		length = fullId
	}

	// Check that we got exactly one node
	args = flags.Args()
	if len(args) != 1 {
		c.Ui.Error(c.Help())
		return 1
	}

	// Read the Jobfile
	path := args[0]
	var f io.Reader = os.Stdin
	if path != "-" {
		file, err := os.Open(path)
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error opening file: %s", err))
			return 1
		}
		defer file.Close()
		f = file
	}

	// Parse the JobFile
	job, err := jobspec.Parse(f)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error parsing job file %s: %s", f, err))
		return 1
	}

	// Initialize any fields that need to be.
	job.InitFields()

	// Check that the job is valid
	if err := job.Validate(); err != nil {
		c.Ui.Error(fmt.Sprintf("Error validating job: %s", err))
		return 1
	}

	// Check if the job is periodic.
	periodic := job.IsPeriodic()

	// Convert it to something we can use
	apiJob, err := convertStructJob(job)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error converting job: %s", err))
		return 1
	}

	if output {
		req := api.RegisterJobRequest{Job: apiJob}
		buf, err := json.MarshalIndent(req, "", "    ")
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error converting job: %s", err))
			return 1
		}

		c.Ui.Output(string(buf))
		return 0
	}

	// Get the HTTP client
	client, err := c.Meta.Client()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
		return 1
	}

	// Force the region to be that of the job.
	if r := job.Region; r != "" {
		client.SetRegion(r)
	}

	// Parse the check-index
	checkIndex, enforce, err := parseCheckIndex(checkIndexStr)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error parsing check-index value %q: %v", checkIndexStr, err))
		return 1
	}

	// Submit the job
	var evalID string
	if enforce {
		evalID, _, err = client.Jobs().EnforceRegister(apiJob, checkIndex, nil)
	} else {
		evalID, _, err = client.Jobs().Register(apiJob, nil)
	}
	if err != nil {
		if strings.Contains(err.Error(), api.RegisterEnforceIndexErrPrefix) {
			// Format the error specially if the error is due to index
			// enforcement
			matches := enforceIndexRegex.FindStringSubmatch(err.Error())
			if len(matches) == 2 {
				c.Ui.Error(matches[1]) // The matched group
				c.Ui.Error("Job not updated")
				return 1
			}
		}

		c.Ui.Error(fmt.Sprintf("Error submitting job: %s", err))
		return 1
	}

	// Check if we should enter monitor mode
	if detach || periodic {
		c.Ui.Output("Job registration successful")
		if periodic {
			now := time.Now().UTC()
			next := job.Periodic.Next(now)
			c.Ui.Output(fmt.Sprintf("Approximate next launch time: %s (%s from now)",
				formatTime(next), formatTimeDifference(now, next, time.Second)))
		} else {
			c.Ui.Output("Evaluation ID: " + evalID)
		}

		return 0
	}

	// Detach was not specified, so start monitoring
	mon := newMonitor(c.Ui, client, length)
	return mon.monitor(evalID, false)

}

// parseCheckIndex parses the check-index flag and returns the index, whether it
// was set and potentially an error during parsing.
func parseCheckIndex(input string) (uint64, bool, error) {
	if input == "" {
		return 0, false, nil
	}

	u, err := strconv.ParseUint(input, 10, 64)
	return u, true, err
}

// convertStructJob is used to take a *structs.Job and convert it to an *api.Job.
// This function is just a hammer and probably needs to be revisited.
func convertStructJob(in *structs.Job) (*api.Job, error) {
	gob.Register([]map[string]interface{}{})
	gob.Register([]interface{}{})
	var apiJob *api.Job
	buf := new(bytes.Buffer)
	if err := gob.NewEncoder(buf).Encode(in); err != nil {
		return nil, err
	}
	if err := gob.NewDecoder(buf).Decode(&apiJob); err != nil {
		return nil, err
	}
	return apiJob, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2014 The SkyDNS Authors. All rights reserved.
// Use of this source code is governed by The MIT License (MIT) that can be
// found in the LICENSE file.

package server

import (
	"log"
	"net"
	"strconv"
	"strings"

	"github.com/miekg/dns"
	"github.com/skynetservices/skydns/msg"
)

const ednsStubCode = dns.EDNS0LOCALSTART + 10

// ednsStub is the EDNS0 record we add to stub queries. Queries which have this record are
// not forwarded again.
var ednsStub = func() *dns.OPT {
	o := new(dns.OPT)
	o.Hdr.Name = "."
	o.Hdr.Rrtype = dns.TypeOPT
	e := new(dns.EDNS0_LOCAL)
	e.Code = ednsStubCode
	e.Data = []byte{1}
	o.Option = append(o.Option, e)
	return o
}()

// Look in .../dns/stub/<domain>/xx for msg.Services. Loop through them
// extract <domain> and add them as forwarders (ip:port-combos) for
// the stubzones. Only numeric (i.e. IP address) hosts are used.
func (s *server) UpdateStubZones() {
	stubmap := make(map[string][]string)

	services, err := s.backend.Records("stub.dns."+s.config.Domain, false)
	if err != nil {
		log.Printf("skydns: stubzone update failed: %s", err)
		return
	}
	for _, serv := range services {
		if serv.Port == 0 {
			serv.Port = 53
		}
		ip := net.ParseIP(serv.Host)
		if ip == nil {
			log.Printf("skydns: stubzone non-address %s seen for: %s", serv.Key, serv.Host)
			continue
		}

		domain := msg.Domain(serv.Key)
		// Chop of left most label, because that is used as the nameserver place holder
		// and drop the right most labels that belong to localDomain.
		labels := dns.SplitDomainName(domain)
		domain = dns.Fqdn(strings.Join(labels[1:len(labels)-dns.CountLabel(s.config.localDomain)], "."))

		// if domain if the remaining name equals s.config.LocalDomain we ignore it.
		if domain == s.config.localDomain {
			log.Printf("skydns: not adding stub zone for my own domain")
			continue
		}
		stubmap[domain] = append(stubmap[domain], net.JoinHostPort(serv.Host, strconv.Itoa(serv.Port)))
	}

	s.config.stub = &stubmap
}

// ServeDNSForward forwards a request to a nameservers and returns the response.
func (s *server) ServeDNSStubForward(w dns.ResponseWriter, req *dns.Msg, ns []string) {
	StatsStubForwardCount.Inc(1)

	// Check EDNS0 Stub option, if set drop the packet.
	option := req.IsEdns0()
	if option != nil {
		for _, o := range option.Option {
			if o.Option() == ednsStubCode && len(o.(*dns.EDNS0_LOCAL).Data) == 1 &&
				o.(*dns.EDNS0_LOCAL).Data[0] == 1 {
				// Maybe log source IP here?
				log.Printf("skydns: not fowarding stub request to another stub")
				return
			}
		}
	}

	tcp := false
	if _, ok := w.RemoteAddr().(*net.TCPAddr); ok {
		tcp = true
	}
	// Add a custom EDNS0 option to the packet, so we can detect loops
	// when 2 stubs are forwarding to each other.
	if option != nil {
		option.Option = append(option.Option, &dns.EDNS0_LOCAL{ednsStubCode, []byte{1}})
	} else {
		req.Extra = append(req.Extra, ednsStub)
	}

	var (
		r   *dns.Msg
		err error
		try int
	)

	// Use request Id for "random" nameserver selection.
	nsid := int(req.Id) % len(ns)
Redo:
	switch tcp {
	case false:
		r, _, err = s.dnsUDPclient.Exchange(req, ns[nsid])
	case true:
		r, _, err = s.dnsTCPclient.Exchange(req, ns[nsid])
	}
	if err == nil {
		r.Compress = true
		r.Id = req.Id
		w.WriteMsg(r)
		return
	}
	// Seen an error, this can only mean, "server not reached", try again
	// but only if we have not exausted our nameservers.
	if try < len(ns) {
		try++
		nsid = (nsid + 1) % len(ns)
		goto Redo
	}

	log.Printf("skydns: failure to forward stub request %q", err)
	m := new(dns.Msg)
	m.SetReply(req)
	m.SetRcode(req, dns.RcodeServerFailure)
	w.WriteMsg(m)
}
<commit_msg>stubzone -> stub zone<commit_after>// Copyright (c) 2014 The SkyDNS Authors. All rights reserved.
// Use of this source code is governed by The MIT License (MIT) that can be
// found in the LICENSE file.

package server

import (
	"log"
	"net"
	"strconv"
	"strings"

	"github.com/miekg/dns"
	"github.com/skynetservices/skydns/msg"
)

const ednsStubCode = dns.EDNS0LOCALSTART + 10

// ednsStub is the EDNS0 record we add to stub queries. Queries which have this record are
// not forwarded again.
var ednsStub = func() *dns.OPT {
	o := new(dns.OPT)
	o.Hdr.Name = "."
	o.Hdr.Rrtype = dns.TypeOPT
	e := new(dns.EDNS0_LOCAL)
	e.Code = ednsStubCode
	e.Data = []byte{1}
	o.Option = append(o.Option, e)
	return o
}()

// Look in .../dns/stub/<domain>/xx for msg.Services. Loop through them
// extract <domain> and add them as forwarders (ip:port-combos) for
// the stub zones. Only numeric (i.e. IP address) hosts are used.
func (s *server) UpdateStubZones() {
	stubmap := make(map[string][]string)

	services, err := s.backend.Records("stub.dns."+s.config.Domain, false)
	if err != nil {
		log.Printf("skydns: stub zone update failed: %s", err)
		return
	}
	for _, serv := range services {
		if serv.Port == 0 {
			serv.Port = 53
		}
		ip := net.ParseIP(serv.Host)
		if ip == nil {
			log.Printf("skydns: stub zone non-address %s seen for: %s", serv.Key, serv.Host)
			continue
		}

		domain := msg.Domain(serv.Key)
		// Chop of left most label, because that is used as the nameserver place holder
		// and drop the right most labels that belong to localDomain.
		labels := dns.SplitDomainName(domain)
		domain = dns.Fqdn(strings.Join(labels[1:len(labels)-dns.CountLabel(s.config.localDomain)], "."))

		// if domain if the remaining name equals s.config.LocalDomain we ignore it.
		if domain == s.config.localDomain {
			log.Printf("skydns: not adding stub zone for my own domain")
			continue
		}
		stubmap[domain] = append(stubmap[domain], net.JoinHostPort(serv.Host, strconv.Itoa(serv.Port)))
	}

	s.config.stub = &stubmap
}

// ServeDNSForward forwards a request to a nameservers and returns the response.
func (s *server) ServeDNSStubForward(w dns.ResponseWriter, req *dns.Msg, ns []string) {
	StatsStubForwardCount.Inc(1)

	// Check EDNS0 Stub option, if set drop the packet.
	option := req.IsEdns0()
	if option != nil {
		for _, o := range option.Option {
			if o.Option() == ednsStubCode && len(o.(*dns.EDNS0_LOCAL).Data) == 1 &&
				o.(*dns.EDNS0_LOCAL).Data[0] == 1 {
				// Maybe log source IP here?
				log.Printf("skydns: not fowarding stub request to another stub")
				return
			}
		}
	}

	tcp := false
	if _, ok := w.RemoteAddr().(*net.TCPAddr); ok {
		tcp = true
	}
	// Add a custom EDNS0 option to the packet, so we can detect loops
	// when 2 stubs are forwarding to each other.
	if option != nil {
		option.Option = append(option.Option, &dns.EDNS0_LOCAL{ednsStubCode, []byte{1}})
	} else {
		req.Extra = append(req.Extra, ednsStub)
	}

	var (
		r   *dns.Msg
		err error
		try int
	)

	// Use request Id for "random" nameserver selection.
	nsid := int(req.Id) % len(ns)
Redo:
	switch tcp {
	case false:
		r, _, err = s.dnsUDPclient.Exchange(req, ns[nsid])
	case true:
		r, _, err = s.dnsTCPclient.Exchange(req, ns[nsid])
	}
	if err == nil {
		r.Compress = true
		r.Id = req.Id
		w.WriteMsg(r)
		return
	}
	// Seen an error, this can only mean, "server not reached", try again
	// but only if we have not exausted our nameservers.
	if try < len(ns) {
		try++
		nsid = (nsid + 1) % len(ns)
		goto Redo
	}

	log.Printf("skydns: failure to forward stub request %q", err)
	m := new(dns.Msg)
	m.SetReply(req)
	m.SetRcode(req, dns.RcodeServerFailure)
	w.WriteMsg(m)
}
<|endoftext|>


<commit_before>package ethutil

import (
	"flag"
	"fmt"
	"github.com/rakyll/globalconf"
	"os"
	"runtime"
)

// Config struct
type config struct {
	Db Database

	ExecPath     string
	Debug        bool
	Ver          string
	ClientString string
	Pubkey       []byte
	Identifier   string

	conf *globalconf.GlobalConf
}

var Config *config

// Read config
//
// Initialize Config from Config File
func ReadConfig(ConfigFile string, Datadir string, Identifier string, EnvPrefix string) *config {
	if Config == nil {
		// create ConfigFile if does not exist, otherwise globalconf panic when trying to persist flags
		_, err := os.Stat(ConfigFile)
		if err != nil && os.IsNotExist(err) {
			fmt.Printf("config file '%s' doesn't exist, creating it\n", ConfigFile)
			os.Create(ConfigFile)
		}
		g, err := globalconf.NewWithOptions(&globalconf.Options{
			Filename:  ConfigFile,
			EnvPrefix: EnvPrefix,
		})
		if err != nil {
			fmt.Println(err)
		} else {
			g.ParseAll()
		}
		Config = &config{ExecPath: Datadir, Debug: true, Ver: "0.5.15", conf: g, Identifier: Identifier}
		Config.SetClientString("Ethereum(G)")
	}
	return Config
}

// Set client string
//
func (c *config) SetClientString(str string) {
	os := runtime.GOOS
	cust := c.Identifier
	Config.ClientString = fmt.Sprintf("%s/v%s/%s/%s/Go", str, c.Ver, cust, os)
}

func (c *config) SetIdentifier(id string) {
	c.Identifier = id
	c.Set("id", id)
}

// provides persistence for flags
func (c *config) Set(key, value string) {
	f := &flag.Flag{Name: key, Value: &confValue{value}}
	c.conf.Set("", f)
}

type confValue struct {
	value string
}

func (self confValue) String() string     { return self.value }
func (self confValue) Set(s string) error { self.value = s; return nil }
<commit_msg>config Pubkey removed (unused)<commit_after>package ethutil

import (
	"flag"
	"fmt"
	"github.com/rakyll/globalconf"
	"os"
	"runtime"
)

// Config struct
type config struct {
	Db Database

	ExecPath     string
	Debug        bool
	Ver          string
	ClientString string
	Identifier   string

	conf *globalconf.GlobalConf
}

var Config *config

// Read config
//
// Initialize Config from Config File
func ReadConfig(ConfigFile string, Datadir string, Identifier string, EnvPrefix string) *config {
	if Config == nil {
		// create ConfigFile if does not exist, otherwise globalconf panic when trying to persist flags
		_, err := os.Stat(ConfigFile)
		if err != nil && os.IsNotExist(err) {
			fmt.Printf("config file '%s' doesn't exist, creating it\n", ConfigFile)
			os.Create(ConfigFile)
		}
		g, err := globalconf.NewWithOptions(&globalconf.Options{
			Filename:  ConfigFile,
			EnvPrefix: EnvPrefix,
		})
		if err != nil {
			fmt.Println(err)
		} else {
			g.ParseAll()
		}
		Config = &config{ExecPath: Datadir, Debug: true, Ver: "0.5.15", conf: g, Identifier: Identifier}
		Config.SetClientString("Ethereum(G)")
	}
	return Config
}

// Set client string
//
func (c *config) SetClientString(str string) {
	os := runtime.GOOS
	cust := c.Identifier
	Config.ClientString = fmt.Sprintf("%s/v%s/%s/%s/Go", str, c.Ver, cust, os)
}

func (c *config) SetIdentifier(id string) {
	c.Identifier = id
	c.Set("id", id)
}

// provides persistence for flags
func (c *config) Set(key, value string) {
	f := &flag.Flag{Name: key, Value: &confValue{value}}
	c.conf.Set("", f)
}

type confValue struct {
	value string
}

func (self confValue) String() string     { return self.value }
func (self confValue) Set(s string) error { self.value = s; return nil }
<|endoftext|>


<commit_before>// Copyright (c) 2013-2014 by Michael Dvorkin. All Rights Reserved.
// Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file.

package donna

import ()

type PawnEntry struct {
	hash     uint64
	score    Score
	passers  [2]Bitmask 	// Passed pawn bitmasks for both sides.
}

var pawnCache [8192]PawnEntry

func (e *Evaluation) analyzePawns() {
	hashPawn := e.position.hashPawn
	index := hashPawn % uint64(len(pawnCache))
	e.pawns = &pawnCache[index]

	// Bypass pawns cache if evaluation tracing is enabled.
	if e.pawns.hash != hashPawn || Settings.Trace {
		white, black := e.pawnStructure(White), e.pawnStructure(Black)
		e.pawns.score.clear().add(white).subtract(black)
		e.pawns.hash = e.position.hashPawn
		if Settings.Trace {
			e.checkpoint(`Pawns`, Total{white, black})
		}
	}

	e.score.add(e.pawns.score)
}

func (e *Evaluation) analyzePassers() {
	var white, black Score

	if Settings.Trace {
		defer func() {
			e.checkpoint(`Passers`, Total{white, black})
		}()
	}

	white, black = e.pawnPassers(White), e.pawnPassers(Black)

	e.score.add(white).subtract(black)
}

// Calculates extra bonus and penalty based on pawn structure. Specifically,
// a bonus is awarded for passed pawns, and penalty applied for isolated and
// doubled pawns.
func (e *Evaluation) pawnStructure(color int) (score Score) {
	hisPawns := e.position.outposts[pawn(color)]
	herPawns := e.position.outposts[pawn(color^1)]
	e.pawns.passers[color] = 0

	pawns := hisPawns
	for pawns != 0 {
		square := pawns.pop()
		row, col := Coordinate(square)

		// Penalty if the pawn is isolated, i.e. has no friendly pawns
		// on adjacent files. The penalty goes up if isolated pawn is
		// exposed on semi-open file.
		isolated := (maskIsolated[col] & hisPawns == 0)
		exposed := (maskInFront[color][square] & herPawns == 0)
		if isolated {
			if !exposed {
				score.subtract(penaltyIsolatedPawn[col])
			} else {
				score.subtract(penaltyWeakIsolatedPawn[col])
			}
		}

		// Penalty if the pawn is doubled, i.e. there is another friendly
		// pawn in front of us. The penalty goes up if doubled pawns are
		// isolated.
		doubled := (maskInFront[color][square] & hisPawns != 0)
		if doubled {
			score.subtract(penaltyDoubledPawn[col])
		}

		// Bonus if the pawn is supported by friendly pawn(s) on the same
		// or previous ranks.
		supported := (maskIsolated[col] & (maskRank[row] | maskRank[row].pushed(color^1)) & hisPawns != 0)
		if supported {
			flip := Flip(color, square)
			score.add(Score{bonusSupportedPawn[flip], bonusSupportedPawn[flip]})
		}

		// The pawn is passed if a) there are no enemy pawns in the same
		// and adjacent columns; and b) there are no same color pawns in
		// front of us.
		passed := (maskPassed[color][square] & herPawns == 0 && !doubled)
		if passed {
			e.pawns.passers[color] |= bit[square]
		}

		// Penalty if the pawn is backward.
		if (!passed && !supported && !isolated) {

			// Backward pawn should not be attacking enemy pawns.
			if pawnMoves[color][square] & herPawns == 0 {

				// Backward pawn should not have friendly pawns behind.
				if maskPassed[color^1][square] & maskIsolated[col] & hisPawns == 0 {

					// Backward pawn should face enemy pawns on the next two ranks
					// preventing its advance.
					enemy := pawnMoves[color][square].pushed(color)
					if (enemy | enemy.pushed(color)) & herPawns != 0 {
						if !exposed {
							score.subtract(penaltyBackwardPawn[col])
						} else {
							score.subtract(penaltyWeakBackwardPawn[col])
						}
					}
				}
			}
		}

		// TODO: Bonus if the pawn has good chance to become a passed pawn.
	}

	return
}

func (e *Evaluation) pawnPassers(color int) (score Score) {
	p := e.position

	pawns := e.pawns.passers[color]
	for pawns != 0 {
		square := pawns.pop()
		row := RelRow(square, color)
		bonus := bonusPassedPawn[row]

		if row > A2H2 {
			nextSquare := square + eight[color]

			// Check if the pawn can step forward.
			if p.board.isClear(nextSquare) {

				// Assume all squares in front of the pawn are under attack.
				attacked := maskInFront[color][square]
				protected := attacked & e.attacks[color]

				// Check if the assumption is true and whether there is a queen
				// or a rook attacking our passed pawn from behind.
				enemy := maskInFront[color^1][square] & (p.outposts[queen(color^1)] | p.outposts[rook(color^1)])
				if enemy == 0 || enemy & p.rookMoves(square) == 0 {

					// Since nobody attacks the pawn from behind adjust the attacked
					// bitmask to only include squares attacked or occupied by the enemy.
					attacked &= (e.attacks[color^1] | p.outposts[color^1])

				}

				// Boost the bonus if passed pawn is free to run to the 8th rank
				// or at least safely step forward.
				extra := 0
				if attacked == 0 {
					extra = 10
				} else if attacked.isClear(nextSquare) {
					extra = 6
				}

				// Boost the bonus even more if all the squares in front of the
				// pawn are protected. If not, see if next square is protected.
				if protected == maskInFront[color][square] {
					extra += 4
				} else if protected.isSet(nextSquare) {
					extra += 2
				}
				if extra > 0 {
					bonus.adjust(extra * extraPassedPawn[row])
				}
			}
			// TODO: Adjust bonus based on proximity of both kings.
		}
		score.add(bonus)
	}

	// Penalty for blocked pawns.
	blocked := (p.outposts[pawn(color)].pushed(color) & p.board).count()
	score.subtract(pawnBlocked.times(blocked))

	return
}

<commit_msg>Use faster 32-bit indexing for pawns cache<commit_after>// Copyright (c) 2013-2014 by Michael Dvorkin. All Rights Reserved.
// Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file.

package donna

import ()

type PawnEntry struct {
	hash     uint64 	// Pawn hash key.
	score    Score 		// Static score for the given pawn structure.
	passers  [2]Bitmask 	// Passed pawn bitmasks for both sides.
}

var pawnCache [8192]PawnEntry

func (e *Evaluation) analyzePawns() {
	key := e.position.hashPawn

	// Since pawn hash is fairly small we can use much faster 32-bit index.
	index := uint32(key) % uint32(len(pawnCache))
	e.pawns = &pawnCache[index]

	// Bypass pawns cache if evaluation tracing is enabled.
	if e.pawns.hash != key || Settings.Trace {
		white, black := e.pawnStructure(White), e.pawnStructure(Black)
		e.pawns.score.clear().add(white).subtract(black)
		e.pawns.hash = key
		if Settings.Trace {
			e.checkpoint(`Pawns`, Total{white, black})
		}
	}

	e.score.add(e.pawns.score)
}

func (e *Evaluation) analyzePassers() {
	var white, black Score

	if Settings.Trace {
		defer func() {
			e.checkpoint(`Passers`, Total{white, black})
		}()
	}

	white, black = e.pawnPassers(White), e.pawnPassers(Black)

	e.score.add(white).subtract(black)
}

// Calculates extra bonus and penalty based on pawn structure. Specifically,
// a bonus is awarded for passed pawns, and penalty applied for isolated and
// doubled pawns.
func (e *Evaluation) pawnStructure(color int) (score Score) {
	hisPawns := e.position.outposts[pawn(color)]
	herPawns := e.position.outposts[pawn(color^1)]
	e.pawns.passers[color] = 0

	pawns := hisPawns
	for pawns != 0 {
		square := pawns.pop()
		row, col := Coordinate(square)

		// Penalty if the pawn is isolated, i.e. has no friendly pawns
		// on adjacent files. The penalty goes up if isolated pawn is
		// exposed on semi-open file.
		isolated := (maskIsolated[col] & hisPawns == 0)
		exposed := (maskInFront[color][square] & herPawns == 0)
		if isolated {
			if !exposed {
				score.subtract(penaltyIsolatedPawn[col])
			} else {
				score.subtract(penaltyWeakIsolatedPawn[col])
			}
		}

		// Penalty if the pawn is doubled, i.e. there is another friendly
		// pawn in front of us. The penalty goes up if doubled pawns are
		// isolated.
		doubled := (maskInFront[color][square] & hisPawns != 0)
		if doubled {
			score.subtract(penaltyDoubledPawn[col])
		}

		// Bonus if the pawn is supported by friendly pawn(s) on the same
		// or previous ranks.
		supported := (maskIsolated[col] & (maskRank[row] | maskRank[row].pushed(color^1)) & hisPawns != 0)
		if supported {
			flip := Flip(color, square)
			score.add(Score{bonusSupportedPawn[flip], bonusSupportedPawn[flip]})
		}

		// The pawn is passed if a) there are no enemy pawns in the same
		// and adjacent columns; and b) there are no same color pawns in
		// front of us.
		passed := (maskPassed[color][square] & herPawns == 0 && !doubled)
		if passed {
			e.pawns.passers[color] |= bit[square]
		}

		// Penalty if the pawn is backward.
		if (!passed && !supported && !isolated) {

			// Backward pawn should not be attacking enemy pawns.
			if pawnMoves[color][square] & herPawns == 0 {

				// Backward pawn should not have friendly pawns behind.
				if maskPassed[color^1][square] & maskIsolated[col] & hisPawns == 0 {

					// Backward pawn should face enemy pawns on the next two ranks
					// preventing its advance.
					enemy := pawnMoves[color][square].pushed(color)
					if (enemy | enemy.pushed(color)) & herPawns != 0 {
						if !exposed {
							score.subtract(penaltyBackwardPawn[col])
						} else {
							score.subtract(penaltyWeakBackwardPawn[col])
						}
					}
				}
			}
		}

		// TODO: Bonus if the pawn has good chance to become a passed pawn.
	}

	return
}

func (e *Evaluation) pawnPassers(color int) (score Score) {
	p := e.position

	pawns := e.pawns.passers[color]
	for pawns != 0 {
		square := pawns.pop()
		row := RelRow(square, color)
		bonus := bonusPassedPawn[row]

		if row > A2H2 {
			nextSquare := square + eight[color]

			// Check if the pawn can step forward.
			if p.board.isClear(nextSquare) {

				// Assume all squares in front of the pawn are under attack.
				attacked := maskInFront[color][square]
				protected := attacked & e.attacks[color]

				// Check if the assumption is true and whether there is a queen
				// or a rook attacking our passed pawn from behind.
				enemy := maskInFront[color^1][square] & (p.outposts[queen(color^1)] | p.outposts[rook(color^1)])
				if enemy == 0 || enemy & p.rookMoves(square) == 0 {

					// Since nobody attacks the pawn from behind adjust the attacked
					// bitmask to only include squares attacked or occupied by the enemy.
					attacked &= (e.attacks[color^1] | p.outposts[color^1])

				}

				// Boost the bonus if passed pawn is free to run to the 8th rank
				// or at least safely step forward.
				extra := 0
				if attacked == 0 {
					extra = 10
				} else if attacked.isClear(nextSquare) {
					extra = 6
				}

				// Boost the bonus even more if all the squares in front of the
				// pawn are protected. If not, see if next square is protected.
				if protected == maskInFront[color][square] {
					extra += 4
				} else if protected.isSet(nextSquare) {
					extra += 2
				}
				if extra > 0 {
					bonus.adjust(extra * extraPassedPawn[row])
				}
			}
			// TODO: Adjust bonus based on proximity of both kings.
		}
		score.add(bonus)
	}

	// Penalty for blocked pawns.
	blocked := (p.outposts[pawn(color)].pushed(color) & p.board).count()
	score.subtract(pawnBlocked.times(blocked))

	return
}

<|endoftext|>


<commit_before>//
// Copyright 2018, Patrick Webster
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package gitlab

import (
	"fmt"
)

// GroupIssueBoardsService handles communication with the group issue board
// related methods of the GitLab API.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html
type GroupIssueBoardsService struct {
	client *Client
}

// GroupIssueBoard represents a GitLab group issue board.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html
type GroupIssueBoard struct {
	ID        int          `json:"id"`
	Name      string       `json:"name"`
	Group     *Group       `json:"group"`
	Milestone *Milestone   `json:"milestone"`
	Lists     []*BoardList `json:"lists"`
}

func (b GroupIssueBoard) String() string {
	return Stringify(b)
}

// ListGroupIssueBoardsOptions represents the available
// ListGroupIssueBoards() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#group-board
type ListGroupIssueBoardsOptions ListOptions

// ListGroupIssueBoards gets a list of all issue boards in a group.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#group-board
func (s *GroupIssueBoardsService) ListGroupIssueBoards(gid interface{}, opt *ListGroupIssueBoardsOptions, options ...OptionFunc) ([]*GroupIssueBoard, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards", pathEscape(group))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var gs []*GroupIssueBoard
	resp, err := s.client.Do(req, &gs)
	if err != nil {
		return nil, resp, err
	}

	return gs, resp, err
}

// CreateGroupIssueBoardOptions represents the available
// CreateGroupIssueBoard() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#create-a-group-issue-board-premium
type CreateGroupIssueBoardOptions struct {
	Name *string `url:"name" json:"name"`
}

// CreateGroupIssueBoard creates a new issue board.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#create-a-group-issue-board-premium
func (s *GroupIssueBoardsService) CreateGroupIssueBoard(gid interface{}, opt *CreateGroupIssueBoardOptions, options ...OptionFunc) (*GroupIssueBoard, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards", pathEscape(group))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	gib := new(GroupIssueBoard)
	resp, err := s.client.Do(req, gib)
	if err != nil {
		return nil, resp, err
	}

	return gib, resp, err
}

// GetGroupIssueBoard gets a single issue board of a group.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#single-board
func (s *GroupIssueBoardsService) GetGroupIssueBoard(gid interface{}, board int, options ...OptionFunc) (*GroupIssueBoard, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d", pathEscape(group), board)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	gib := new(GroupIssueBoard)
	resp, err := s.client.Do(req, gib)
	if err != nil {
		return nil, resp, err
	}

	return gib, resp, err
}

type UpdateGroupIssueBoardOptions struct {
	ID          *string `url:"id" json:"id"`
	BoardID     *int    `url:"board_id" json:"board_id"`
	Name        *string `url:"name" json:"name"`
	AssigneeID  *int    `url:"assignee_id" json:"assignee_id"`
	MilestoneID *int    `url:"milestone_id" json:"milestone_id"`
	Labels      *string `url:"labels" json:"labels"`
	Weight      *int    `url:"weight" json:"weight"`
}

// UpdateIssueBoard updates a single issue board of a group.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#update-a-group-issue-board-premium
func (s *GroupIssueBoardsService) UpdateIssueBoard(gid interface{}, board int, opt *UpdateGroupIssueBoardOptions, options ...OptionFunc) (*GroupIssueBoard, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d",
		pathEscape(group),
		board,
	)

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var gib GroupIssueBoard
	resp, err := s.client.Do(req, gbl)
	if err != nil {
		return nil, resp, err
	}

	return gib, resp, err
}

// DeleteIssueBoard delete a single issue board of a group.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#delete-a-group-issue-board-premium
func (s *GroupIssueBoardsService) DeleteIssueBoard(gid interface{}, board int, options ...OptionFunc) (*Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d",
		pathEscape(group),
		board,
	)

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// ListGroupIssueBoardListsOptions represents the available
// ListGroupIssueBoardLists() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#list-board-lists
type ListGroupIssueBoardListsOptions ListOptions

// ListGroupIssueBoardLists gets a list of the issue board's lists. Does not include
// backlog and closed lists.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/group_boards.html#list-board-lists
func (s *GroupIssueBoardsService) ListGroupIssueBoardLists(gid interface{}, board int, opt *ListGroupIssueBoardListsOptions, options ...OptionFunc) ([]*BoardList, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d/lists", pathEscape(group), board)

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var gbl []*BoardList
	resp, err := s.client.Do(req, &gbl)
	if err != nil {
		return nil, resp, err
	}

	return gbl, resp, err
}

// GetGroupIssueBoardList gets a single issue board list.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#single-board-list
func (s *GroupIssueBoardsService) GetGroupIssueBoardList(gid interface{}, board, list int, options ...OptionFunc) (*BoardList, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d/lists/%d",
		pathEscape(group),
		board,
		list,
	)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	gbl := new(BoardList)
	resp, err := s.client.Do(req, gbl)
	if err != nil {
		return nil, resp, err
	}

	return gbl, resp, err
}

// CreateGroupIssueBoardListOptions represents the available
// CreateGroupIssueBoardList() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#new-board-list
type CreateGroupIssueBoardListOptions struct {
	LabelID *int `url:"label_id" json:"label_id"`
}

// CreateGroupIssueBoardList creates a new issue board list.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#new-board-list
func (s *GroupIssueBoardsService) CreateGroupIssueBoardList(gid interface{}, board int, opt *CreateGroupIssueBoardListOptions, options ...OptionFunc) (*BoardList, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d/lists", pathEscape(group), board)

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	gbl := new(BoardList)
	resp, err := s.client.Do(req, gbl)
	if err != nil {
		return nil, resp, err
	}

	return gbl, resp, err
}

// UpdateGroupIssueBoardListOptions represents the available
// UpdateGroupIssueBoardList() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#edit-board-list
type UpdateGroupIssueBoardListOptions struct {
	Position *int `url:"position" json:"position"`
}

// UpdateIssueBoardList updates the position of an existing
// group issue board list.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#edit-board-list
func (s *GroupIssueBoardsService) UpdateIssueBoardList(gid interface{}, board, list int, opt *UpdateGroupIssueBoardListOptions, options ...OptionFunc) ([]*BoardList, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d/lists/%d",
		pathEscape(group),
		board,
		list,
	)

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var gbl []*BoardList
	resp, err := s.client.Do(req, gbl)
	if err != nil {
		return nil, resp, err
	}

	return gbl, resp, err
}

// DeleteGroupIssueBoardList soft deletes a group issue board list.
// Only for admins and group owners.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#delete-a-board-list
func (s *GroupIssueBoardsService) DeleteGroupIssueBoardList(gid interface{}, board, list int, options ...OptionFunc) (*Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d/lists/%d",
		pathEscape(group),
		board,
		list,
	)

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<commit_msg>Fixed UpdateGroupIssueBoardOptions type<commit_after>//
// Copyright 2018, Patrick Webster
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package gitlab

import (
	"fmt"
)

// GroupIssueBoardsService handles communication with the group issue board
// related methods of the GitLab API.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html
type GroupIssueBoardsService struct {
	client *Client
}

// GroupIssueBoard represents a GitLab group issue board.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html
type GroupIssueBoard struct {
	ID        int          `json:"id"`
	Name      string       `json:"name"`
	Group     *Group       `json:"group"`
	Milestone *Milestone   `json:"milestone"`
	Lists     []*BoardList `json:"lists"`
}

func (b GroupIssueBoard) String() string {
	return Stringify(b)
}

// ListGroupIssueBoardsOptions represents the available
// ListGroupIssueBoards() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#group-board
type ListGroupIssueBoardsOptions ListOptions

// ListGroupIssueBoards gets a list of all issue boards in a group.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#group-board
func (s *GroupIssueBoardsService) ListGroupIssueBoards(gid interface{}, opt *ListGroupIssueBoardsOptions, options ...OptionFunc) ([]*GroupIssueBoard, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards", pathEscape(group))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var gs []*GroupIssueBoard
	resp, err := s.client.Do(req, &gs)
	if err != nil {
		return nil, resp, err
	}

	return gs, resp, err
}

// CreateGroupIssueBoardOptions represents the available
// CreateGroupIssueBoard() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#create-a-group-issue-board-premium
type CreateGroupIssueBoardOptions struct {
	Name *string `url:"name" json:"name"`
}

// CreateGroupIssueBoard creates a new issue board.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#create-a-group-issue-board-premium
func (s *GroupIssueBoardsService) CreateGroupIssueBoard(gid interface{}, opt *CreateGroupIssueBoardOptions, options ...OptionFunc) (*GroupIssueBoard, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards", pathEscape(group))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	gib := new(GroupIssueBoard)
	resp, err := s.client.Do(req, gib)
	if err != nil {
		return nil, resp, err
	}

	return gib, resp, err
}

// GetGroupIssueBoard gets a single issue board of a group.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#single-board
func (s *GroupIssueBoardsService) GetGroupIssueBoard(gid interface{}, board int, options ...OptionFunc) (*GroupIssueBoard, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d", pathEscape(group), board)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	gib := new(GroupIssueBoard)
	resp, err := s.client.Do(req, gib)
	if err != nil {
		return nil, resp, err
	}

	return gib, resp, err
}

type UpdateGroupIssueBoardOptions struct {
	Name        *string `url:"name,omitempty" json:"name,omitempty"`
	AssigneeID  *int    `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
	MilestoneID *int    `url:"milestone_id,omitempty" json:"milestone_id,omitempty"`
	Labels      *string `url:"labels,omitempty" json:"labels,omitempty"`
	Weight      *int    `url:"weight,omitempty" json:"weight,omitempty"`
}

// UpdateIssueBoard updates a single issue board of a group.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#update-a-group-issue-board-premium
func (s *GroupIssueBoardsService) UpdateIssueBoard(gid interface{}, board int, opt *UpdateGroupIssueBoardOptions, options ...OptionFunc) (*GroupIssueBoard, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d", pathEscape(group), board)

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var gib GroupIssueBoard
	resp, err := s.client.Do(req, gbl)
	if err != nil {
		return nil, resp, err
	}

	return gib, resp, err
}

// DeleteIssueBoard delete a single issue board of a group.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#delete-a-group-issue-board-premium
func (s *GroupIssueBoardsService) DeleteIssueBoard(gid interface{}, board int, options ...OptionFunc) (*Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d", pathEscape(group), board)

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// ListGroupIssueBoardListsOptions represents the available
// ListGroupIssueBoardLists() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#list-board-lists
type ListGroupIssueBoardListsOptions ListOptions

// ListGroupIssueBoardLists gets a list of the issue board's lists. Does not include
// backlog and closed lists.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/group_boards.html#list-board-lists
func (s *GroupIssueBoardsService) ListGroupIssueBoardLists(gid interface{}, board int, opt *ListGroupIssueBoardListsOptions, options ...OptionFunc) ([]*BoardList, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d/lists", pathEscape(group), board)

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var gbl []*BoardList
	resp, err := s.client.Do(req, &gbl)
	if err != nil {
		return nil, resp, err
	}

	return gbl, resp, err
}

// GetGroupIssueBoardList gets a single issue board list.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#single-board-list
func (s *GroupIssueBoardsService) GetGroupIssueBoardList(gid interface{}, board, list int, options ...OptionFunc) (*BoardList, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d/lists/%d",
		pathEscape(group),
		board,
		list,
	)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	gbl := new(BoardList)
	resp, err := s.client.Do(req, gbl)
	if err != nil {
		return nil, resp, err
	}

	return gbl, resp, err
}

// CreateGroupIssueBoardListOptions represents the available
// CreateGroupIssueBoardList() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#new-board-list
type CreateGroupIssueBoardListOptions struct {
	LabelID *int `url:"label_id" json:"label_id"`
}

// CreateGroupIssueBoardList creates a new issue board list.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#new-board-list
func (s *GroupIssueBoardsService) CreateGroupIssueBoardList(gid interface{}, board int, opt *CreateGroupIssueBoardListOptions, options ...OptionFunc) (*BoardList, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d/lists", pathEscape(group), board)

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	gbl := new(BoardList)
	resp, err := s.client.Do(req, gbl)
	if err != nil {
		return nil, resp, err
	}

	return gbl, resp, err
}

// UpdateGroupIssueBoardListOptions represents the available
// UpdateGroupIssueBoardList() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#edit-board-list
type UpdateGroupIssueBoardListOptions struct {
	Position *int `url:"position" json:"position"`
}

// UpdateIssueBoardList updates the position of an existing
// group issue board list.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#edit-board-list
func (s *GroupIssueBoardsService) UpdateIssueBoardList(gid interface{}, board, list int, opt *UpdateGroupIssueBoardListOptions, options ...OptionFunc) ([]*BoardList, *Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d/lists/%d",
		pathEscape(group),
		board,
		list,
	)

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var gbl []*BoardList
	resp, err := s.client.Do(req, gbl)
	if err != nil {
		return nil, resp, err
	}

	return gbl, resp, err
}

// DeleteGroupIssueBoardList soft deletes a group issue board list.
// Only for admins and group owners.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/group_boards.html#delete-a-board-list
func (s *GroupIssueBoardsService) DeleteGroupIssueBoardList(gid interface{}, board, list int, options ...OptionFunc) (*Response, error) {
	group, err := parseID(gid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("groups/%s/boards/%d/lists/%d",
		pathEscape(group),
		board,
		list,
	)

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Type aliases are available only in Go 1.9 and later.
// +build go1.9

package zaptest

import "go.uber.org/zap/internal/ztest"

type (
	// A Syncer is a spy for the Sync portion of zapcore.WriteSyncer.
	Syncer = ztest.Syncer

	// A Discarder sends all writes to ioutil.Discard.
	Discarder = ztest.Discarder

	// FailWriter is a WriteSyncer that always returns an error on writes.
	FailWriter = ztest.FailWriter

	// ShortWriter is a WriteSyncer whose write method never fails, but
	// nevertheless fails to the last byte of the input.
	ShortWriter = ztest.ShortWriter

	// Buffer is an implementation of zapcore.WriteSyncer that sends all writes to
	// a bytes.Buffer. It has convenience methods to split the accumulated buffer
	// on newlines.
	Buffer = ztest.Buffer
)
<commit_msg>Clarify ShortWriter documentation (#575)<commit_after>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Type aliases are available only in Go 1.9 and later.
// +build go1.9

package zaptest

import "go.uber.org/zap/internal/ztest"

type (
	// A Syncer is a spy for the Sync portion of zapcore.WriteSyncer.
	Syncer = ztest.Syncer

	// A Discarder sends all writes to ioutil.Discard.
	Discarder = ztest.Discarder

	// FailWriter is a WriteSyncer that always returns an error on writes.
	FailWriter = ztest.FailWriter

	// ShortWriter is a WriteSyncer whose write method never returns an error,
	// but always reports that it wrote one byte less than the input slice's
	// length (thus, a "short write").
	ShortWriter = ztest.ShortWriter

	// Buffer is an implementation of zapcore.WriteSyncer that sends all writes to
	// a bytes.Buffer. It has convenience methods to split the accumulated buffer
	// on newlines.
	Buffer = ztest.Buffer
)
<|endoftext|>


<commit_before>package main

import (
	"context"
	"crypto/tls"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"time"

	"github.com/evepraisal/go-evepraisal"
	"github.com/evepraisal/go-evepraisal/bolt"
	"github.com/evepraisal/go-evepraisal/esi"
	"github.com/evepraisal/go-evepraisal/management"
	"github.com/evepraisal/go-evepraisal/parsers"
	"github.com/evepraisal/go-evepraisal/staticdump"
	"github.com/evepraisal/go-evepraisal/typedb"
	"github.com/evepraisal/go-evepraisal/web"
	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
	"github.com/gregjones/httpcache"
	"github.com/newrelic/go-agent"
	"github.com/sethgrid/pester"
	"github.com/spf13/viper"
	"golang.org/x/crypto/acme/autocert"
	"golang.org/x/oauth2"
)

func appMain() {
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt)

	log.Println("Starting price DB")
	priceDB, err := bolt.NewPriceDB(filepath.Join(viper.GetString("db_path"), "prices"))
	if err != nil {
		log.Fatalf("Couldn't start price database: %s", err)
	}
	defer func() {
		err := priceDB.Close()
		if err != nil {
			log.Fatalf("Problem closing priceDB: %s", err)
		}
	}()

	httpCache, err := bolt.NewHTTPCache(filepath.Join(viper.GetString("db_path"), "httpcache"))
	if err != nil {
		log.Fatalf("Couldn't start httpCache: %s", err)
	}
	defer func() {
		err := httpCache.Close()
		if err != nil {
			log.Fatalf("Problem closing httpCache: %s", err)
		}
	}()

	defer func() {
		err := priceDB.Close()
		if err != nil {
			log.Fatalf("Problem closing priceDB: %s", err)
		}
	}()

	httpClient := pester.New()
	httpClient.Transport = httpcache.NewTransport(httpCache)
	httpClient.Concurrency = 5
	httpClient.Timeout = 10 * time.Second
	httpClient.Backoff = pester.ExponentialJitterBackoff
	httpClient.MaxRetries = 10

	priceFetcher, err := esi.NewPriceFetcher(priceDB, viper.GetString("esi_baseurl"), httpClient)
	if err != nil {
		log.Fatalf("Couldn't start price fetcher: %s", err)
	}
	defer func() {
		err := priceFetcher.Close()
		if err != nil {
			log.Fatalf("Problem closing priceDB: %s", err)
		}
	}()

	log.Println("Starting appraisal DB")
	appraisalDB, err := bolt.NewAppraisalDB(filepath.Join(viper.GetString("db_path"), "appraisals"))
	if err != nil {
		log.Fatalf("Couldn't start appraisal database: %s", err)
	}
	defer func() {
		err := appraisalDB.Close()
		if err != nil {
			log.Fatalf("Problem closing appraisalDB: %s", err)
		}
	}()

	app := &evepraisal.App{
		AppraisalDB: appraisalDB,
		PriceDB:     priceDB,
	}

	log.Println("Starting type fetcher")
	staticdumpHTTPClient := pester.New()
	staticdumpHTTPClient.Concurrency = 1
	staticdumpHTTPClient.Timeout = 5 * time.Minute
	staticdumpHTTPClient.Backoff = pester.ExponentialJitterBackoff
	staticdumpHTTPClient.MaxRetries = 10

	if viper.GetString("newrelic_license-key") != "" {
		newRelicConfig := newrelic.NewConfig(viper.GetString("newrelic_app-name"), viper.GetString("newrelic_license-key"))
		newRelicApplication, err := newrelic.NewApplication(newRelicConfig)
		if err != nil {
			log.Fatalf("Problem configuring new relic: %s", err)
		}

		app.NewRelicApplication = newRelicApplication
		httpClient.Transport = NewRoundTripper(newRelicApplication, httpClient.Transport)
		staticdumpHTTPClient.Transport = NewRoundTripper(newRelicApplication, nil)
	}

	staticFetcher, err := staticdump.NewStaticFetcher(staticdumpHTTPClient, viper.GetString("db_path"), func(typeDB typedb.TypeDB) {
		oldTypeDB := app.TypeDB
		app.TypeDB = typeDB
		app.Parser = evepraisal.NewContextMultiParser(
			typeDB,
			[]parsers.Parser{
				parsers.ParseKillmail,
				parsers.ParseEFT,
				parsers.ParseFitting,
				parsers.ParseLootHistory,
				parsers.ParsePI,
				parsers.ParseViewContents,
				parsers.ParseWallet,
				parsers.ParseSurveyScan,
				parsers.ParseContract,
				parsers.ParseAssets,
				parsers.ParseIndustry,
				parsers.ParseCargoScan,
				parsers.ParseDScan,
				parsers.NewContextListingParser(typeDB),
				parsers.NewHeuristicParser(typeDB),
			})

		if oldTypeDB != nil {
			log.Println("closeing old typedb")
			oldTypeDB.Close()
			log.Println("closed old typedb")
		}
	})
	if err != nil {
		log.Fatalf("Couldn't start static fetcher: %s", err)
	}
	defer func() {
		err := staticFetcher.Close()
		if err != nil {
			log.Fatalf("Problem closing static fetcher: %s", err)
		}

		if app.TypeDB != nil {
			err = app.TypeDB.Close()
			if err != nil {
				log.Fatalf("Problem closing typeDB: %s", err)
			}
		}
	}()

	webContext := web.NewContext(app)
	webContext.BaseURL = strings.TrimSuffix(viper.GetString("base-url"), "/")
	webContext.ExtraJS = viper.GetString("extra-js")
	webContext.AdBlock = viper.GetString("ad-block")
	if viper.GetString("cookie-auth-key") != "" {
		webContext.CookieStore = sessions.NewCookieStore(
			[]byte(viper.GetString("cookie-auth-key")),
			[]byte(viper.GetString("cookie-encryption-key")))
	} else {
		webContext.CookieStore = sessions.NewCookieStore(securecookie.GenerateRandomKey(32))
	}
	if viper.GetString("sso-client-id") != "" {
		webContext.OauthConfig = &oauth2.Config{
			ClientID:     viper.GetString("sso-client-id"),
			ClientSecret: viper.GetString("sso-client-secret"),
			Scopes:       []string{},
			Endpoint: oauth2.Endpoint{
				AuthURL:  viper.GetString("sso-authorize-url"),
				TokenURL: viper.GetString("sso-token-url"),
			},
			RedirectURL: viper.GetString("base-url") + "/oauthcallback",
		}
		webContext.OauthVerifyURL = viper.GetString("sso-verify-url")
	}

	app.WebContext = webContext

	servers := mustStartServers(app.WebContext.HTTPHandler())
	if err != nil {
		log.Fatalf("Problem starting https server: %s", err)
	}

	for _, server := range servers {
		stopCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer server.Shutdown(stopCtx)
		go func() {
			time.Sleep(10 * time.Second)
			cancel()
		}()
	}

	startEnvironmentWatchers(app)

	log.Printf("Starting Management HTTP server (%s)", viper.GetString("management_addr"))
	mgmtServer := &http.Server{
		Addr:    viper.GetString("management_addr"),
		Handler: management.HTTPHandler(app),
	}
	defer mgmtServer.Close()
	go func() {
		err := mgmtServer.ListenAndServe()
		if err == http.ErrServerClosed {
			log.Println("Management HTTP server stopped")
		} else if err != nil {
			log.Fatalf("Management HTTP server failure: %s", err)
		}
	}()

	<-stop
	log.Println("Shutting down")
}

func mustStartServers(handler http.Handler) []*http.Server {
	servers := make([]*http.Server, 0)

	if viper.GetString("https_addr") != "" {
		log.Printf("Starting HTTPS server (%s) (%s)", viper.GetString("https_addr"), viper.GetStringSlice("https_domain-whitelist"))

		autocertManager := autocert.Manager{
			Prompt:     autocert.AcceptTOS,
			HostPolicy: autocert.HostWhitelist(viper.GetStringSlice("https_domain-whitelist")...),
			Cache:      autocert.DirCache(filepath.Join(viper.GetString("db_path"), "certs")),
			Email:      viper.GetString("letsencrypt_email"),
		}

		server := &http.Server{
			Addr:      viper.GetString("https_addr"),
			Handler:   handler,
			TLSConfig: &tls.Config{GetCertificate: autocertManager.GetCertificate},
		}
		servers = append(servers, server)

		go func() {
			err := server.ListenAndServeTLS("", "")
			if err == http.ErrServerClosed {
				log.Println("HTTPS server stopped")
			} else if err != nil {
				log.Fatalf("HTTPS server failure: %s", err)
			}
		}()
		time.Sleep(1 * time.Second)
	}

	if viper.GetString("http_addr") != "" {
		log.Printf("Starting HTTP server (%s)", viper.GetString("http_addr"))

		server := &http.Server{
			Addr:    viper.GetString("http_addr"),
			Handler: handler,
		}
		servers = append(servers, server)

		go func() {
			err := server.ListenAndServe()
			if err == http.ErrServerClosed {
				log.Println("HTTP server stopped")
			} else if err != nil {
				log.Fatalf("HTTP server failure: %s", err)
			}
		}()
	}

	return servers
}

func NewRoundTripper(newrelicApp newrelic.Application, original http.RoundTripper) http.RoundTripper {
	if original == nil {
		original = http.DefaultTransport
	}

	return roundTripperFunc(func(request *http.Request) (*http.Response, error) {
		txn := newrelicApp.StartTransaction("http", nil, nil)
		segment := newrelic.StartExternalSegment(txn, request)
		response, err := original.RoundTrip(request)
		segment.Response = response
		segment.End()
		txn.End()

		return response, err
	})
}

type roundTripperFunc func(*http.Request) (*http.Response, error)

func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
<commit_msg>Move context listing parser up<commit_after>package main

import (
	"context"
	"crypto/tls"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"time"

	"github.com/evepraisal/go-evepraisal"
	"github.com/evepraisal/go-evepraisal/bolt"
	"github.com/evepraisal/go-evepraisal/esi"
	"github.com/evepraisal/go-evepraisal/management"
	"github.com/evepraisal/go-evepraisal/parsers"
	"github.com/evepraisal/go-evepraisal/staticdump"
	"github.com/evepraisal/go-evepraisal/typedb"
	"github.com/evepraisal/go-evepraisal/web"
	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
	"github.com/gregjones/httpcache"
	"github.com/newrelic/go-agent"
	"github.com/sethgrid/pester"
	"github.com/spf13/viper"
	"golang.org/x/crypto/acme/autocert"
	"golang.org/x/oauth2"
)

func appMain() {
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt)

	log.Println("Starting price DB")
	priceDB, err := bolt.NewPriceDB(filepath.Join(viper.GetString("db_path"), "prices"))
	if err != nil {
		log.Fatalf("Couldn't start price database: %s", err)
	}
	defer func() {
		err := priceDB.Close()
		if err != nil {
			log.Fatalf("Problem closing priceDB: %s", err)
		}
	}()

	httpCache, err := bolt.NewHTTPCache(filepath.Join(viper.GetString("db_path"), "httpcache"))
	if err != nil {
		log.Fatalf("Couldn't start httpCache: %s", err)
	}
	defer func() {
		err := httpCache.Close()
		if err != nil {
			log.Fatalf("Problem closing httpCache: %s", err)
		}
	}()

	defer func() {
		err := priceDB.Close()
		if err != nil {
			log.Fatalf("Problem closing priceDB: %s", err)
		}
	}()

	httpClient := pester.New()
	httpClient.Transport = httpcache.NewTransport(httpCache)
	httpClient.Concurrency = 5
	httpClient.Timeout = 10 * time.Second
	httpClient.Backoff = pester.ExponentialJitterBackoff
	httpClient.MaxRetries = 10

	priceFetcher, err := esi.NewPriceFetcher(priceDB, viper.GetString("esi_baseurl"), httpClient)
	if err != nil {
		log.Fatalf("Couldn't start price fetcher: %s", err)
	}
	defer func() {
		err := priceFetcher.Close()
		if err != nil {
			log.Fatalf("Problem closing priceDB: %s", err)
		}
	}()

	log.Println("Starting appraisal DB")
	appraisalDB, err := bolt.NewAppraisalDB(filepath.Join(viper.GetString("db_path"), "appraisals"))
	if err != nil {
		log.Fatalf("Couldn't start appraisal database: %s", err)
	}
	defer func() {
		err := appraisalDB.Close()
		if err != nil {
			log.Fatalf("Problem closing appraisalDB: %s", err)
		}
	}()

	app := &evepraisal.App{
		AppraisalDB: appraisalDB,
		PriceDB:     priceDB,
	}

	log.Println("Starting type fetcher")
	staticdumpHTTPClient := pester.New()
	staticdumpHTTPClient.Concurrency = 1
	staticdumpHTTPClient.Timeout = 5 * time.Minute
	staticdumpHTTPClient.Backoff = pester.ExponentialJitterBackoff
	staticdumpHTTPClient.MaxRetries = 10

	if viper.GetString("newrelic_license-key") != "" {
		newRelicConfig := newrelic.NewConfig(viper.GetString("newrelic_app-name"), viper.GetString("newrelic_license-key"))
		newRelicApplication, err := newrelic.NewApplication(newRelicConfig)
		if err != nil {
			log.Fatalf("Problem configuring new relic: %s", err)
		}

		app.NewRelicApplication = newRelicApplication
		httpClient.Transport = NewRoundTripper(newRelicApplication, httpClient.Transport)
		staticdumpHTTPClient.Transport = NewRoundTripper(newRelicApplication, nil)
	}

	staticFetcher, err := staticdump.NewStaticFetcher(staticdumpHTTPClient, viper.GetString("db_path"), func(typeDB typedb.TypeDB) {
		oldTypeDB := app.TypeDB
		app.TypeDB = typeDB
		app.Parser = evepraisal.NewContextMultiParser(
			typeDB,
			[]parsers.Parser{
				parsers.ParseKillmail,
				parsers.ParseEFT,
				parsers.ParseFitting,
				parsers.ParseLootHistory,
				parsers.ParsePI,
				parsers.ParseViewContents,
				parsers.ParseWallet,
				parsers.ParseSurveyScan,
				parsers.ParseContract,
				parsers.NewContextListingParser(typeDB),
				parsers.ParseAssets,
				parsers.ParseIndustry,
				parsers.ParseCargoScan,
				parsers.ParseDScan,
				parsers.NewHeuristicParser(typeDB),
			})

		if oldTypeDB != nil {
			log.Println("closeing old typedb")
			oldTypeDB.Close()
			log.Println("closed old typedb")
		}
	})
	if err != nil {
		log.Fatalf("Couldn't start static fetcher: %s", err)
	}
	defer func() {
		err := staticFetcher.Close()
		if err != nil {
			log.Fatalf("Problem closing static fetcher: %s", err)
		}

		if app.TypeDB != nil {
			err = app.TypeDB.Close()
			if err != nil {
				log.Fatalf("Problem closing typeDB: %s", err)
			}
		}
	}()

	webContext := web.NewContext(app)
	webContext.BaseURL = strings.TrimSuffix(viper.GetString("base-url"), "/")
	webContext.ExtraJS = viper.GetString("extra-js")
	webContext.AdBlock = viper.GetString("ad-block")
	if viper.GetString("cookie-auth-key") != "" {
		webContext.CookieStore = sessions.NewCookieStore(
			[]byte(viper.GetString("cookie-auth-key")),
			[]byte(viper.GetString("cookie-encryption-key")))
	} else {
		webContext.CookieStore = sessions.NewCookieStore(securecookie.GenerateRandomKey(32))
	}
	if viper.GetString("sso-client-id") != "" {
		webContext.OauthConfig = &oauth2.Config{
			ClientID:     viper.GetString("sso-client-id"),
			ClientSecret: viper.GetString("sso-client-secret"),
			Scopes:       []string{},
			Endpoint: oauth2.Endpoint{
				AuthURL:  viper.GetString("sso-authorize-url"),
				TokenURL: viper.GetString("sso-token-url"),
			},
			RedirectURL: viper.GetString("base-url") + "/oauthcallback",
		}
		webContext.OauthVerifyURL = viper.GetString("sso-verify-url")
	}

	app.WebContext = webContext

	servers := mustStartServers(app.WebContext.HTTPHandler())
	if err != nil {
		log.Fatalf("Problem starting https server: %s", err)
	}

	for _, server := range servers {
		stopCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer server.Shutdown(stopCtx)
		go func() {
			time.Sleep(10 * time.Second)
			cancel()
		}()
	}

	startEnvironmentWatchers(app)

	log.Printf("Starting Management HTTP server (%s)", viper.GetString("management_addr"))
	mgmtServer := &http.Server{
		Addr:    viper.GetString("management_addr"),
		Handler: management.HTTPHandler(app),
	}
	defer mgmtServer.Close()
	go func() {
		err := mgmtServer.ListenAndServe()
		if err == http.ErrServerClosed {
			log.Println("Management HTTP server stopped")
		} else if err != nil {
			log.Fatalf("Management HTTP server failure: %s", err)
		}
	}()

	<-stop
	log.Println("Shutting down")
}

func mustStartServers(handler http.Handler) []*http.Server {
	servers := make([]*http.Server, 0)

	if viper.GetString("https_addr") != "" {
		log.Printf("Starting HTTPS server (%s) (%s)", viper.GetString("https_addr"), viper.GetStringSlice("https_domain-whitelist"))

		autocertManager := autocert.Manager{
			Prompt:     autocert.AcceptTOS,
			HostPolicy: autocert.HostWhitelist(viper.GetStringSlice("https_domain-whitelist")...),
			Cache:      autocert.DirCache(filepath.Join(viper.GetString("db_path"), "certs")),
			Email:      viper.GetString("letsencrypt_email"),
		}

		server := &http.Server{
			Addr:      viper.GetString("https_addr"),
			Handler:   handler,
			TLSConfig: &tls.Config{GetCertificate: autocertManager.GetCertificate},
		}
		servers = append(servers, server)

		go func() {
			err := server.ListenAndServeTLS("", "")
			if err == http.ErrServerClosed {
				log.Println("HTTPS server stopped")
			} else if err != nil {
				log.Fatalf("HTTPS server failure: %s", err)
			}
		}()
		time.Sleep(1 * time.Second)
	}

	if viper.GetString("http_addr") != "" {
		log.Printf("Starting HTTP server (%s)", viper.GetString("http_addr"))

		server := &http.Server{
			Addr:    viper.GetString("http_addr"),
			Handler: handler,
		}
		servers = append(servers, server)

		go func() {
			err := server.ListenAndServe()
			if err == http.ErrServerClosed {
				log.Println("HTTP server stopped")
			} else if err != nil {
				log.Fatalf("HTTP server failure: %s", err)
			}
		}()
	}

	return servers
}

func NewRoundTripper(newrelicApp newrelic.Application, original http.RoundTripper) http.RoundTripper {
	if original == nil {
		original = http.DefaultTransport
	}

	return roundTripperFunc(func(request *http.Request) (*http.Response, error) {
		txn := newrelicApp.StartTransaction("http", nil, nil)
		segment := newrelic.StartExternalSegment(txn, request)
		response, err := original.RoundTrip(request)
		segment.Response = response
		segment.End()
		txn.End()

		return response, err
	})
}

type roundTripperFunc func(*http.Request) (*http.Response, error)

func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/base64"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

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

var fullTests = []struct {
	cnt      int
	Req      string
	Expected []string
}{
	{
		cnt: 2,
		Req: `255 <158>1 2015-04-02T11:52:34.520012+00:00 host heroku router - at=info method=POST path="/users" host=myapp.com request_id=c1806361-2081-42e7-a8aa-92b6808eac8e fwd="24.76.242.18" dyno=web.1 connect=1ms service=37ms status=201 bytes=828`,
		Expected: []string{
			"heroku.router.request.connect:1.000000|h|#at:info,dyno:web.1,host:myapp.com,method:POST,path:/users,status:201,statusFamily:2xx",
			"heroku.router.request.service:37.000000|h|#at:info,dyno:web.1,host:myapp.com,method:POST,path:/users,status:201,statusFamily:2xx",
		},
	},
	{
		cnt: 1,
		Req: `229 <45>1 2015-04-02T11:48:16.839257+00:00 host heroku web.1 - source=web.1 dyno=heroku.35930502.b9de5fce-44b7-4287-99a7-504519070cba sample#load_avg_1m=0.01`,
		Expected: []string{
			"heroku.dyno.load.avg.1m:0.010000|g|#source:web.1,type:web",
		},
	},
	{
		cnt: 3,
		Req: `222 <134>1 2015-04-07T16:01:43.517062+00:00 host app api - Scaled to web@3:Performance-L mailer@1:Standard-2X by user someuser@gmail.com`,
		Expected: []string{
			"_e{16,77}:heroku/api: test|Scaled to web@3:Performance-L mailer@1:Standard-2X by user someuser@gmail.com",
			"heroku.dyno.mailer:1.000000|g",
			"heroku.dyno.web:3.000000|g",
		},
	},
	{
		cnt: 1,
		Req: `222 <134>1 2015-04-07T16:01:43.517062+00:00 host app api - Release v1 created by foo@bar`,
		Expected: []string{
			"_e{13,29}:app/api: test|Release v1 created by foo@bar",
		},
	},
	{
		cnt: 9,
		Req: `452 <134>1 2015-04-07T16:01:43.517062+00:00 host app web.1 - info: responseLogger: metric#tag#route=/parser metric#request_id=11747467-f4ce-4b06-8c99-92be968a02e3 metric#request_length=541 metric#response_length=5163 metric#parser_time=5ms metric#eventLoop.count=606 metric#eventLoop.avg_ms=515.503300330033 metric#eventLoop.p50_ms=0.8805309734513275 metric#eventLoop.p95_ms=3457.206896551724 metric#eventLoop.p99_ms=3457.206896551724 metric#eventLoop.max_ms=5008`,
		Expected: []string{
			"app.metric.request.length:541.000000|g|#source:web.1,type:web,route:/parser",
			"app.metric.response.length:5163.000000|g|#source:web.1,type:web,route:/parser",
			"app.metric.parser.time:5.000000|g|#source:web.1,type:web,route:/parser",
			"app.metric.eventLoop.count:606.000000|g|#source:web.1,type:web,route:/parser",
			"app.metric.eventLoop.avg.ms:515.503300|g|#source:web.1,type:web,route:/parser",
			"app.metric.eventLoop.p50.ms:0.880531|g|#source:web.1,type:web,route:/parser",
			"app.metric.eventLoop.p95.ms:3457.206897|g|#source:web.1,type:web,route:/parser",
			"app.metric.eventLoop.p99.ms:3457.206897|g|#source:web.1,type:web,route:/parser",
			"app.metric.eventLoop.max.ms:5008.000000|g|#source:web.1,type:web,route:/parser",
		},
	},
}

func TestStatusRequest(t *testing.T) {

	r := gin.New()
	r.GET("/status", func(c *gin.Context) {
		c.String(200, "OK")
	})

	req, _ := http.NewRequest("GET", "/status", nil)
	resp := httptest.NewRecorder()
	r.ServeHTTP(resp, req)

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Error(err)
	}

	if string(body) != "OK" {
		t.Error("resp body should match")
	}

	if resp.Code != 200 {
		t.Error("should get a 200")
	}
}

func basicAuth(username, password string) string {
	auth := username + ":" + password
	return base64.StdEncoding.EncodeToString([]byte(auth))
}

func TestLogRequest(t *testing.T) {

	s := loadServerCtx()
	s.AllowedApps = append(s.AllowedApps, "test")
	s.AppPasswd["test"] = "pass"

	s.in = make(chan *logData)
	defer close(s.in)
	s.out = make(chan *logMetrics)
	defer close(s.out)

	go logProcess(s.in, s.out)

	r := gin.New()
	auth := r.Group("/", gin.BasicAuth(s.AppPasswd))
	auth.POST("/", s.processLogs)

	req, _ := http.NewRequest("POST", "/", bytes.NewBuffer([]byte("LINE of text\nAnother line\n")))
	req.SetBasicAuth("test", "pass")
	resp := httptest.NewRecorder()
	r.ServeHTTP(resp, req)

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Error(err)
	}
	if string(body) != "OK" {
		t.Error("resp body should match")
	}

	if resp.Code != 200 {
		t.Error("should get a 200")
	}

}

func TestFull(t *testing.T) {

	s := loadServerCtx()
	s.AllowedApps = append(s.AllowedApps, "test")
	s.AppPasswd["test"] = "pass"

	s.in = make(chan *logData)
	defer close(s.in)
	s.out = make(chan *logMetrics)
	defer close(s.out)

	addr := "localhost:1201"
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		t.Fatal(err)
	}

	server, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		t.Fatal(err)
	}
	defer server.Close()

	c, err := statsdClient(addr)
	if err != nil {
		t.Fatal(err)
	}

	go logProcess(s.in, s.out)
	go c.sendToStatsd(s.out)

	r := gin.New()
	auth := r.Group("/", gin.BasicAuth(s.AppPasswd))
	auth.POST("/", s.processLogs)

	data := make([]byte, 1024)
	for _, tt := range fullTests {
		req, _ := http.NewRequest("POST", "/", bytes.NewBuffer([]byte(tt.Req)))
		req.SetBasicAuth("test", "pass")
		resp := httptest.NewRecorder()
		r.ServeHTTP(resp, req)

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Error(err)
		}
		if string(body) != "OK" {
			t.Error("resp body should match")
		}
		if resp.Code != 200 {
			t.Error("should get a 200")
		}
		if tt.cnt != len(tt.Expected) {
			t.Error("Count of expected results isn't equal to inputs")
		}
		for i := 0; i < tt.cnt; i++ {
			n, err := server.Read(data)
			if err != nil {
				t.Fatal(err)
			}
			message := data[:n]
			findEqual := false

			for j := 0; j < len(tt.Expected); j++ {
				if string(message) == tt.Expected[j] {
					findEqual = true
				}
			}
			if findEqual == false {
				t.Errorf("Expected: %s. Actual: %s", tt.Expected[i], string(message))
			}
		}
	}

	time.Sleep(1 * time.Second)
}
<commit_msg>tests for parsing dashes in tag values<commit_after>package main

import (
	"bytes"
	"encoding/base64"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

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

var fullTests = []struct {
	cnt      int
	Req      string
	Expected []string
}{
	{
		cnt: 2,
		Req: `255 <158>1 2015-04-02T11:52:34.520012+00:00 host heroku router - at=info method=POST path="/users" host=myapp.com request_id=c1806361-2081-42e7-a8aa-92b6808eac8e fwd="24.76.242.18" dyno=web.1 connect=1ms service=37ms status=201 bytes=828`,
		Expected: []string{
			"heroku.router.request.connect:1.000000|h|#at:info,dyno:web.1,host:myapp.com,method:POST,path:/users,status:201,statusFamily:2xx",
			"heroku.router.request.service:37.000000|h|#at:info,dyno:web.1,host:myapp.com,method:POST,path:/users,status:201,statusFamily:2xx",
		},
	},
	{
		cnt: 1,
		Req: `229 <45>1 2015-04-02T11:48:16.839257+00:00 host heroku web.1 - source=web.1 dyno=heroku.35930502.b9de5fce-44b7-4287-99a7-504519070cba sample#load_avg_1m=0.01`,
		Expected: []string{
			"heroku.dyno.load.avg.1m:0.010000|g|#source:web.1,type:web",
		},
	},
	{
		cnt: 3,
		Req: `222 <134>1 2015-04-07T16:01:43.517062+00:00 host app api - Scaled to web@3:Performance-L mailer@1:Standard-2X by user someuser@gmail.com`,
		Expected: []string{
			"_e{16,77}:heroku/api: test|Scaled to web@3:Performance-L mailer@1:Standard-2X by user someuser@gmail.com",
			"heroku.dyno.mailer:1.000000|g",
			"heroku.dyno.web:3.000000|g",
		},
	},
	{
		cnt: 1,
		Req: `222 <134>1 2015-04-07T16:01:43.517062+00:00 host app api - Release v1 created by foo@bar`,
		Expected: []string{
			"_e{13,29}:app/api: test|Release v1 created by foo@bar",
		},
	},
	{
		cnt: 9,
		Req: `452 <134>1 2015-04-07T16:01:43.517062+00:00 host app web.1 - info: responseLogger: metric#tag#route=/parser-page metric#request_id=11747467-f4ce-4b06-8c99-92be968a02e3 metric#request_length=541 metric#response_length=5163 metric#parser_time=5ms metric#eventLoop.count=606 metric#eventLoop.avg_ms=515.503300330033 metric#eventLoop.p50_ms=0.8805309734513275 metric#eventLoop.p95_ms=3457.206896551724 metric#eventLoop.p99_ms=3457.206896551724 metric#eventLoop.max_ms=5008`,
		Expected: []string{
			"app.metric.request.length:541.000000|g|#source:web.1,type:web,route:/parser-page",
			"app.metric.response.length:5163.000000|g|#source:web.1,type:web,route:/parser-page",
			"app.metric.parser.time:5.000000|g|#source:web.1,type:web,route:/parser-page",
			"app.metric.eventLoop.count:606.000000|g|#source:web.1,type:web,route:/parser-page",
			"app.metric.eventLoop.avg.ms:515.503300|g|#source:web.1,type:web,route:/parser-page",
			"app.metric.eventLoop.p50.ms:0.880531|g|#source:web.1,type:web,route:/parser-page",
			"app.metric.eventLoop.p95.ms:3457.206897|g|#source:web.1,type:web,route:/parser-page",
			"app.metric.eventLoop.p99.ms:3457.206897|g|#source:web.1,type:web,route:/parser-page",
			"app.metric.eventLoop.max.ms:5008.000000|g|#source:web.1,type:web,route:/parser-page",
		},
	},
}

func TestStatusRequest(t *testing.T) {

	r := gin.New()
	r.GET("/status", func(c *gin.Context) {
		c.String(200, "OK")
	})

	req, _ := http.NewRequest("GET", "/status", nil)
	resp := httptest.NewRecorder()
	r.ServeHTTP(resp, req)

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Error(err)
	}

	if string(body) != "OK" {
		t.Error("resp body should match")
	}

	if resp.Code != 200 {
		t.Error("should get a 200")
	}
}

func basicAuth(username, password string) string {
	auth := username + ":" + password
	return base64.StdEncoding.EncodeToString([]byte(auth))
}

func TestLogRequest(t *testing.T) {

	s := loadServerCtx()
	s.AllowedApps = append(s.AllowedApps, "test")
	s.AppPasswd["test"] = "pass"

	s.in = make(chan *logData)
	defer close(s.in)
	s.out = make(chan *logMetrics)
	defer close(s.out)

	go logProcess(s.in, s.out)

	r := gin.New()
	auth := r.Group("/", gin.BasicAuth(s.AppPasswd))
	auth.POST("/", s.processLogs)

	req, _ := http.NewRequest("POST", "/", bytes.NewBuffer([]byte("LINE of text\nAnother line\n")))
	req.SetBasicAuth("test", "pass")
	resp := httptest.NewRecorder()
	r.ServeHTTP(resp, req)

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Error(err)
	}
	if string(body) != "OK" {
		t.Error("resp body should match")
	}

	if resp.Code != 200 {
		t.Error("should get a 200")
	}

}

func TestFull(t *testing.T) {

	s := loadServerCtx()
	s.AllowedApps = append(s.AllowedApps, "test")
	s.AppPasswd["test"] = "pass"

	s.in = make(chan *logData)
	defer close(s.in)
	s.out = make(chan *logMetrics)
	defer close(s.out)

	addr := "localhost:1201"
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		t.Fatal(err)
	}

	server, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		t.Fatal(err)
	}
	defer server.Close()

	c, err := statsdClient(addr)
	if err != nil {
		t.Fatal(err)
	}

	go logProcess(s.in, s.out)
	go c.sendToStatsd(s.out)

	r := gin.New()
	auth := r.Group("/", gin.BasicAuth(s.AppPasswd))
	auth.POST("/", s.processLogs)

	data := make([]byte, 1024)
	for _, tt := range fullTests {
		req, _ := http.NewRequest("POST", "/", bytes.NewBuffer([]byte(tt.Req)))
		req.SetBasicAuth("test", "pass")
		resp := httptest.NewRecorder()
		r.ServeHTTP(resp, req)

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Error(err)
		}
		if string(body) != "OK" {
			t.Error("resp body should match")
		}
		if resp.Code != 200 {
			t.Error("should get a 200")
		}
		if tt.cnt != len(tt.Expected) {
			t.Error("Count of expected results isn't equal to inputs")
		}
		for i := 0; i < tt.cnt; i++ {
			n, err := server.Read(data)
			if err != nil {
				t.Fatal(err)
			}
			message := data[:n]
			findEqual := false

			for j := 0; j < len(tt.Expected); j++ {
				if string(message) == tt.Expected[j] {
					findEqual = true
				}
			}
			if findEqual == false {
				t.Errorf("Expected: %s. Actual: %s", tt.Expected[i], string(message))
			}
		}
	}

	time.Sleep(1 * time.Second)
}
<|endoftext|>