<commit_before>package dns

import (
	"context"
	"sync"
	"time"

	"github.com/miekg/dns"
	"v2ray.com/core"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/dice"
	"v2ray.com/core/common/net"
	"v2ray.com/core/common/signal"
	"v2ray.com/core/transport/internet/udp"
)

var (
	multiQuestionDNS = map[net.Address]bool{
		net.IPAddress([]byte{8, 8, 8, 8}): true,
		net.IPAddress([]byte{8, 8, 4, 4}): true,
		net.IPAddress([]byte{9, 9, 9, 9}): true,
	}
)

type ARecord struct {
	IPs    []net.IP
	Expire time.Time
}

type NameServer interface {
	QueryA(domain string) <-chan *ARecord
}

type PendingRequest struct {
	expire   time.Time
	response chan<- *ARecord
}

type UDPNameServer struct {
	sync.Mutex
	address   net.Destination
	requests  map[uint16]*PendingRequest
	udpServer *udp.Dispatcher
	cleanup   *signal.PeriodicTask
}

func NewUDPNameServer(address net.Destination, dispatcher core.Dispatcher) *UDPNameServer {
	s := &UDPNameServer{
		address:   address,
		requests:  make(map[uint16]*PendingRequest),
		udpServer: udp.NewDispatcher(dispatcher),
	}
	s.cleanup = &signal.PeriodicTask{
		Interval: time.Minute,
		Execute:  s.Cleanup,
	}
	s.cleanup.Start()
	return s
}

func (s *UDPNameServer) Cleanup() error {
	now := time.Now()
	s.Lock()
	for id, r := range s.requests {
		if r.expire.Before(now) {
			close(r.response)
			delete(s.requests, id)
		}
	}
	s.Unlock()
	return nil
}

func (s *UDPNameServer) AssignUnusedID(response chan<- *ARecord) uint16 {
	var id uint16
	s.Lock()

	for {
		id = dice.RollUint16()
		if _, found := s.requests[id]; found {
			time.Sleep(time.Millisecond * 500)
			continue
		}
		newError("add pending request id ", id).AtDebug().WriteToLog()
		s.requests[id] = &PendingRequest{
			expire:   time.Now().Add(time.Second * 8),
			response: response,
		}
		break
	}
	s.Unlock()
	return id
}

func (s *UDPNameServer) HandleResponse(payload *buf.Buffer) {
	msg := new(dns.Msg)
	err := msg.Unpack(payload.Bytes())
	if err == dns.ErrTruncated {
		newError("truncated message received. DNS server should still work. If you see anything abnormal, please submit an issue to v2ray-core.").AtWarning().WriteToLog()
	} else if err != nil {
		newError("failed to parse DNS response").Base(err).AtWarning().WriteToLog()
		return
	}
	record := &ARecord{
		IPs: make([]net.IP, 0, 16),
	}
	id := msg.Id
	ttl := uint32(3600) // an hour
	newError("handling response for id ", id, " content: ", msg).AtDebug().WriteToLog()

	s.Lock()
	request, found := s.requests[id]
	if !found {
		s.Unlock()
		return
	}
	delete(s.requests, id)
	s.Unlock()

	for _, rr := range msg.Answer {
		switch rr := rr.(type) {
		case *dns.A:
			record.IPs = append(record.IPs, rr.A)
			if rr.Hdr.Ttl < ttl {
				ttl = rr.Hdr.Ttl
			}
		case *dns.AAAA:
			record.IPs = append(record.IPs, rr.AAAA)
			if rr.Hdr.Ttl < ttl {
				ttl = rr.Hdr.Ttl
			}
		}
	}
	record.Expire = time.Now().Add(time.Second * time.Duration(ttl))

	request.response <- record
	close(request.response)
}

func (s *UDPNameServer) buildAMsg(domain string, id uint16) *dns.Msg {
	msg := new(dns.Msg)
	msg.Id = id
	msg.RecursionDesired = true
	msg.Question = []dns.Question{
		{
			Name:   dns.Fqdn(domain),
			Qtype:  dns.TypeA,
			Qclass: dns.ClassINET,
		}}
	if multiQuestionDNS[s.address.Address] {
		msg.Question = append(msg.Question, dns.Question{
			Name:   dns.Fqdn(domain),
			Qtype:  dns.TypeAAAA,
			Qclass: dns.ClassINET,
		})
	}

	return msg
}

func msgToBuffer(msg *dns.Msg) (*buf.Buffer, error) {
	buffer := buf.New()
	if err := buffer.Reset(func(b []byte) (int, error) {
		writtenBuffer, err := msg.PackBuffer(b)
		return len(writtenBuffer), err
	}); err != nil {
		return nil, err
	}
	return buffer, nil
}

func (s *UDPNameServer) QueryA(domain string) <-chan *ARecord {
	response := make(chan *ARecord, 1)
	id := s.AssignUnusedID(response)

	msg := s.buildAMsg(domain, id)
	b, err := msgToBuffer(msg)
	if err != nil {
		newError("failed to build A query for domain ", domain).Base(err).WriteToLog()
		close(response)
		return response
	}

	ctx, cancel := context.WithCancel(context.Background())
	s.udpServer.Dispatch(ctx, s.address, b, s.HandleResponse)

	go func() {
		for i := 0; i < 2; i++ {
			time.Sleep(time.Second)
			s.Lock()
			_, found := s.requests[id]
			s.Unlock()
			if !found {
				break
			}
			b, _ := msgToBuffer(msg)
			s.udpServer.Dispatch(ctx, s.address, b, s.HandleResponse)
		}
		cancel()
	}()

	return response
}

type LocalNameServer struct {
}

func (*LocalNameServer) QueryA(domain string) <-chan *ARecord {
	response := make(chan *ARecord, 1)

	go func() {
		defer close(response)

		ips, err := net.LookupIP(domain)
		if err != nil {
			newError("failed to lookup IPs for domain ", domain).Base(err).AtWarning().WriteToLog()
			return
		}

		response <- &ARecord{
			IPs:    ips,
			Expire: time.Now().Add(time.Hour),
		}
	}()

	return response
}
<commit_msg>remove pending request from cache when error. fixes #942<commit_after>package dns

import (
	"context"
	"sync"
	"time"

	"github.com/miekg/dns"
	"v2ray.com/core"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/dice"
	"v2ray.com/core/common/net"
	"v2ray.com/core/common/signal"
	"v2ray.com/core/transport/internet/udp"
)

var (
	multiQuestionDNS = map[net.Address]bool{
		net.IPAddress([]byte{8, 8, 8, 8}): true,
		net.IPAddress([]byte{8, 8, 4, 4}): true,
		net.IPAddress([]byte{9, 9, 9, 9}): true,
	}
)

type ARecord struct {
	IPs    []net.IP
	Expire time.Time
}

type NameServer interface {
	QueryA(domain string) <-chan *ARecord
}

type PendingRequest struct {
	expire   time.Time
	response chan<- *ARecord
}

type UDPNameServer struct {
	sync.Mutex
	address   net.Destination
	requests  map[uint16]*PendingRequest
	udpServer *udp.Dispatcher
	cleanup   *signal.PeriodicTask
}

func NewUDPNameServer(address net.Destination, dispatcher core.Dispatcher) *UDPNameServer {
	s := &UDPNameServer{
		address:   address,
		requests:  make(map[uint16]*PendingRequest),
		udpServer: udp.NewDispatcher(dispatcher),
	}
	s.cleanup = &signal.PeriodicTask{
		Interval: time.Minute,
		Execute:  s.Cleanup,
	}
	s.cleanup.Start()
	return s
}

func (s *UDPNameServer) Cleanup() error {
	now := time.Now()
	s.Lock()
	for id, r := range s.requests {
		if r.expire.Before(now) {
			close(r.response)
			delete(s.requests, id)
		}
	}
	s.Unlock()
	return nil
}

func (s *UDPNameServer) AssignUnusedID(response chan<- *ARecord) uint16 {
	var id uint16
	s.Lock()

	for {
		id = dice.RollUint16()
		if _, found := s.requests[id]; found {
			time.Sleep(time.Millisecond * 500)
			continue
		}
		newError("add pending request id ", id).AtDebug().WriteToLog()
		s.requests[id] = &PendingRequest{
			expire:   time.Now().Add(time.Second * 8),
			response: response,
		}
		break
	}
	s.Unlock()
	return id
}

func (s *UDPNameServer) HandleResponse(payload *buf.Buffer) {
	msg := new(dns.Msg)
	err := msg.Unpack(payload.Bytes())
	if err == dns.ErrTruncated {
		newError("truncated message received. DNS server should still work. If you see anything abnormal, please submit an issue to v2ray-core.").AtWarning().WriteToLog()
	} else if err != nil {
		newError("failed to parse DNS response").Base(err).AtWarning().WriteToLog()
		return
	}
	record := &ARecord{
		IPs: make([]net.IP, 0, 16),
	}
	id := msg.Id
	ttl := uint32(3600) // an hour
	newError("handling response for id ", id, " content: ", msg).AtDebug().WriteToLog()

	s.Lock()
	request, found := s.requests[id]
	if !found {
		s.Unlock()
		return
	}
	delete(s.requests, id)
	s.Unlock()

	for _, rr := range msg.Answer {
		switch rr := rr.(type) {
		case *dns.A:
			record.IPs = append(record.IPs, rr.A)
			if rr.Hdr.Ttl < ttl {
				ttl = rr.Hdr.Ttl
			}
		case *dns.AAAA:
			record.IPs = append(record.IPs, rr.AAAA)
			if rr.Hdr.Ttl < ttl {
				ttl = rr.Hdr.Ttl
			}
		}
	}
	record.Expire = time.Now().Add(time.Second * time.Duration(ttl))

	request.response <- record
	close(request.response)
}

func (s *UDPNameServer) buildAMsg(domain string, id uint16) *dns.Msg {
	msg := new(dns.Msg)
	msg.Id = id
	msg.RecursionDesired = true
	msg.Question = []dns.Question{
		{
			Name:   dns.Fqdn(domain),
			Qtype:  dns.TypeA,
			Qclass: dns.ClassINET,
		}}
	if multiQuestionDNS[s.address.Address] {
		msg.Question = append(msg.Question, dns.Question{
			Name:   dns.Fqdn(domain),
			Qtype:  dns.TypeAAAA,
			Qclass: dns.ClassINET,
		})
	}

	return msg
}

func msgToBuffer(msg *dns.Msg) (*buf.Buffer, error) {
	buffer := buf.New()
	if err := buffer.Reset(func(b []byte) (int, error) {
		writtenBuffer, err := msg.PackBuffer(b)
		return len(writtenBuffer), err
	}); err != nil {
		return nil, err
	}
	return buffer, nil
}

func (s *UDPNameServer) QueryA(domain string) <-chan *ARecord {
	response := make(chan *ARecord, 1)
	id := s.AssignUnusedID(response)

	msg := s.buildAMsg(domain, id)
	b, err := msgToBuffer(msg)
	if err != nil {
		newError("failed to build A query for domain ", domain).Base(err).WriteToLog()
		s.Lock()
		delete(s.requests, id)
		s.Unlock()
		close(response)
		return response
	}

	ctx, cancel := context.WithCancel(context.Background())
	s.udpServer.Dispatch(ctx, s.address, b, s.HandleResponse)

	go func() {
		for i := 0; i < 2; i++ {
			time.Sleep(time.Second)
			s.Lock()
			_, found := s.requests[id]
			s.Unlock()
			if !found {
				break
			}
			b, _ := msgToBuffer(msg)
			s.udpServer.Dispatch(ctx, s.address, b, s.HandleResponse)
		}
		cancel()
	}()

	return response
}

type LocalNameServer struct {
}

func (*LocalNameServer) QueryA(domain string) <-chan *ARecord {
	response := make(chan *ARecord, 1)

	go func() {
		defer close(response)

		ips, err := net.LookupIP(domain)
		if err != nil {
			newError("failed to lookup IPs for domain ", domain).Base(err).AtWarning().WriteToLog()
			return
		}

		response <- &ARecord{
			IPs:    ips,
			Expire: time.Now().Add(time.Hour),
		}
	}()

	return response
}
<|endoftext|>


<commit_before>package kiwi

/*
Copyright (c) 2016, Alexander I.Grafov aka Axel
All rights reserved.

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

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

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

* Neither the name of kvlog nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

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

ॐ तारे तुत्तारे तुरे स्व
*/

import (
	"bytes"
	//	"reflect"
	"strings"
	"testing"
)

/* All tests consists of three parts:

- arrange structures and initialize objects for use in tests
- act on testing object
- check and assert on results

These parts separated by empty lines in each test function.
*/

var (
	sampleMixContext = []interface{}{"context1", "value", "context2", 1, "context3", 0.1, "context4", []string{"the", "sample"}}
	sampleMixRecord  = []interface{}{"key1", "value", "key2", 2, 3, 4, "common", []string{"the", "sample"}}
)

// Get records from logger. Helper for testing.
func (l *Logger) getRecords() []pair {
	return l.pairs
}

// Get context from logger. Helper for testing.
func (l *Logger) getContext() []pair {
	return l.context
}

func TestNewLogger(t *testing.T) {
	l := New()

	if l == nil {
		t.Fatal("initalized logger is nil")
	}
}

// Test chaining for Add()
func TestLogger_AddMixChained_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	log.Add("k", "value2").Add("k2", 123).Add("k3", 3.14159265359).Log()

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=\"value2\" k2=123 k3=3.14159265359e+00" {
		t.Fail()
	}

}

// Test logging of integer value.
func TestLogger_LogIntValueIn_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	log.Log("k", 123)

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=123" {
		t.Fail()
	}
}

// Test logging of negative integer value.
func TestLogger_LogNegativeIntValueIn_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	log.Log("k", 123)

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=123" {
		t.Fail()
	}
}

// Test logging of float value in default (scientific) format.
func TestLogger_LogFloatValue_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	log.Log("k", 3.14159265359)

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=3.14159265359e+00" {
		t.Fail()
	}
}

// Test logging of float value in fixed format.
func TestLogger_LogFixedFloatValue_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	FloatFormat = 'f'
	log.Log("k", 3.14159265359)

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=3.14159265359" {
		t.Fail()
	}
	// Turn back to default format.
	FloatFormat = 'e'
}
<commit_msg>Add more unit tests for different datatypes.<commit_after>package kiwi

/*
Copyright (c) 2016, Alexander I.Grafov aka Axel
All rights reserved.

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

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

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

* Neither the name of kvlog nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

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

ॐ तारे तुत्तारे तुरे स्व
*/

import (
	"bytes"
	//	"reflect"
	"strings"
	"testing"
)

/* All tests consists of three parts:

- arrange structures and initialize objects for use in tests
- act on testing object
- check and assert on results

These parts separated by empty lines in each test function.
*/

var (
	sampleMixContext = []interface{}{"context1", "value", "context2", 1, "context3", 0.1, "context4", []string{"the", "sample"}}
	sampleMixRecord  = []interface{}{"key1", "value", "key2", 2, 3, 4, "common", []string{"the", "sample"}}
)

// Get records from logger. Helper for testing.
func (l *Logger) getRecords() []pair {
	return l.pairs
}

// Get context from logger. Helper for testing.
func (l *Logger) getContext() []pair {
	return l.context
}

func TestNewLogger(t *testing.T) {
	l := New()

	if l == nil {
		t.Fatal("initalized logger is nil")
	}
}

// Test logging of string value.
func TestLogger_LogStringValue_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	log.Log("k", "The sample string with a lot of spaces.")

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=\"The sample string with a lot of spaces.\"" {
		t.Fail()
	}
}

// Test logging of byte array.
func TestLogger_LogBytesValue_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	log.Log("k", []byte("The sample string with a lot of spaces."))

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=\"The sample string with a lot of spaces.\"" {
		t.Fail()
	}
}

// Test logging of integer value.
func TestLogger_LogIntValue_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	log.Log("k", 123)

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=123" {
		t.Fail()
	}
}

// Test logging of negative integer value.
func TestLogger_LogNegativeIntValue_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	log.Log("k", 123)

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=123" {
		t.Fail()
	}
}

// Test logging of float value in default (scientific) format.
func TestLogger_LogFloatValue_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	log.Log("k", 3.14159265359)

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=3.14159265359e+00" {
		t.Fail()
	}
}

// Test logging of float value in fixed format.
func TestLogger_LogFixedFloatValue_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	FloatFormat = 'f'
	log.Log("k", 3.14159265359)

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=3.14159265359" {
		t.Fail()
	}
	// Turn back to default format.
	FloatFormat = 'e'
}

// Test logging of boolean value.
func TestLogger_LogBoolValue_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	log.Log("k", true, "k2", false)

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=true k2=false" {
		t.Fail()
	}
}

// Test chaining for Add()
func TestLogger_AddMixChained_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt())
	defer out.Close()

	log.Add("k", "value2").Add("k2", 123).Add("k3", 3.14159265359).Log()

	out.Flush()
	if strings.TrimSpace(output.String()) != "k=\"value2\" k2=123 k3=3.14159265359e+00" {
		t.Fail()
	}

}
<|endoftext|>


<commit_before>// Package datastore specifies and implements the datastore required by the API.
package datastore

import (
	"database/sql"

	dbErrors "github.com/anthonynsimon/parrot/parrot-api/datastore/errors"
	"github.com/anthonynsimon/parrot/parrot-api/datastore/postgres"
	"github.com/anthonynsimon/parrot/parrot-api/model"
)

// Store is the interface that datastores must implement.
type Store interface {
	model.LocaleStorer
	model.ProjectStorer
	model.ProjectLocaleStorer
	model.UserStorer
	model.ProjectUserStorer
	model.ProjectClientStorer
	Ping() error
	Close() error
	MigrateUp(string) error
	MigrateDown(string) error
}

// Datastore is the provided Store implementation.
type Datastore struct {
	Store
}

// NewDatastore creates and configures a new datastore based on the
// parameter name and the connection url.
// Currently only 'postgres' is supported.
func NewDatastore(name string, url string) (*Datastore, error) {
	var ds *Datastore

	switch name {
	case "postgres":
		conn, err := sql.Open("postgres", url)
		if err != nil {
			return nil, err
		}

		p := &postgres.PostgresDB{DB: conn}
		// TODO(anthonynsimon): debug refused connections when db connections > 1
		p.SetMaxIdleConns(1)
		p.SetMaxOpenConns(1)

		ds = &Datastore{p}
	default:
		return nil, dbErrors.ErrNotImplemented
	}

	return ds, nil
}
<commit_msg>Edit TODO<commit_after>// Package datastore specifies and implements the datastore required by the API.
package datastore

import (
	"database/sql"

	dbErrors "github.com/anthonynsimon/parrot/parrot-api/datastore/errors"
	"github.com/anthonynsimon/parrot/parrot-api/datastore/postgres"
	"github.com/anthonynsimon/parrot/parrot-api/model"
)

// Store is the interface that datastores must implement.
type Store interface {
	model.LocaleStorer
	model.ProjectStorer
	model.ProjectLocaleStorer
	model.UserStorer
	model.ProjectUserStorer
	model.ProjectClientStorer
	Ping() error
	Close() error
	MigrateUp(string) error
	MigrateDown(string) error
}

// Datastore is the provided Store implementation.
type Datastore struct {
	Store
}

// NewDatastore creates and configures a new datastore based on the
// parameter name and the connection url.
// Currently only 'postgres' is supported.
func NewDatastore(name string, url string) (*Datastore, error) {
	var ds *Datastore

	switch name {
	case "postgres":
		conn, err := sql.Open("postgres", url)
		if err != nil {
			return nil, err
		}

		p := &postgres.PostgresDB{DB: conn}
		// TODO: debug refused connections when db connections > 1
		p.SetMaxIdleConns(1)
		p.SetMaxOpenConns(1)

		ds = &Datastore{p}
	default:
		return nil, dbErrors.ErrNotImplemented
	}

	return ds, nil
}
<|endoftext|>


<commit_before>package iso

import (
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/packer"
	"os"
)

type toolsUploadPathTemplate struct {
	Flavor string
}

type stepUploadTools struct{}

func (*stepUploadTools) Run(state multistep.StateBag) multistep.StepAction {
	config := state.Get("config").(*config)
	if config.ToolsUploadFlavor == "" {
		return multistep.ActionContinue
	}

	comm := state.Get("communicator").(packer.Communicator)
	tools_source := state.Get("tools_upload_source").(string)
	ui := state.Get("ui").(packer.Ui)

	ui.Say(fmt.Sprintf("Uploading the '%s' VMware Tools", config.ToolsUploadFlavor))
	f, err := os.Open(tools_source)
	if err != nil {
		state.Put("error", fmt.Errorf("Error opening VMware Tools ISO: %s", err))
		return multistep.ActionHalt
	}
	defer f.Close()

	tplData := &toolsUploadPathTemplate{Flavor: config.ToolsUploadFlavor}
	config.ToolsUploadPath, err = config.tpl.Process(config.ToolsUploadPath, tplData)
	if err != nil {
		err := fmt.Errorf("Error preparing upload path: %s", err)
		state.Put("error", err)
		ui.Error(err.Error())
		return multistep.ActionHalt
	}

	if err := comm.Upload(config.ToolsUploadPath, f); err != nil {
		state.Put("error", fmt.Errorf("Error uploading VMware Tools: %s", err))
		return multistep.ActionHalt
	}

	return multistep.ActionContinue
}

func (*stepUploadTools) Cleanup(multistep.StateBag) {}
<commit_msg>builder/vmware-iso: output upload error [GH-899]<commit_after>package iso

import (
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/packer"
	"os"
)

type toolsUploadPathTemplate struct {
	Flavor string
}

type stepUploadTools struct{}

func (*stepUploadTools) Run(state multistep.StateBag) multistep.StepAction {
	config := state.Get("config").(*config)
	if config.ToolsUploadFlavor == "" {
		return multistep.ActionContinue
	}

	comm := state.Get("communicator").(packer.Communicator)
	tools_source := state.Get("tools_upload_source").(string)
	ui := state.Get("ui").(packer.Ui)

	ui.Say(fmt.Sprintf("Uploading the '%s' VMware Tools", config.ToolsUploadFlavor))
	f, err := os.Open(tools_source)
	if err != nil {
		state.Put("error", fmt.Errorf("Error opening VMware Tools ISO: %s", err))
		return multistep.ActionHalt
	}
	defer f.Close()

	tplData := &toolsUploadPathTemplate{Flavor: config.ToolsUploadFlavor}
	config.ToolsUploadPath, err = config.tpl.Process(config.ToolsUploadPath, tplData)
	if err != nil {
		err := fmt.Errorf("Error preparing upload path: %s", err)
		state.Put("error", err)
		ui.Error(err.Error())
		return multistep.ActionHalt
	}

	if err := comm.Upload(config.ToolsUploadPath, f); err != nil {
		err := fmt.Errorf("Error uploading VMware Tools: %s", err)
		state.Put("error", err)
		ui.Error(err.Error())
		return multistep.ActionHalt
	}

	return multistep.ActionContinue
}

func (*stepUploadTools) Cleanup(multistep.StateBag) {}
<|endoftext|>


<commit_before>package chef

import (
	"os"
	"testing"

	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/terraform"
)

// To run these acceptance tests, you will need access to a Chef server.
// An easy way to get one is to sign up for a hosted Chef server account
// at https://manage.chef.io/signup , after which your base URL will
// be something like https://api.opscode.com/organizations/example/ .
// You will also need to create a "client" and write its private key to
// a file somewhere.
//
// You can then set the following environment variables to make these
// tests work:
// CHEF_SERVER_URL to the base URL as described above.
// CHEF_CLIENT_NAME to the name of the client object you created.
// CHEF_PRIVATE_KEY_FILE to the path to the private key file you created.
//
// You will probably need to edit the global permissions on your Chef
// Server account to allow this client (or all clients, if you're lazy)
// to have both List and Create access on all types of object:
//     https://manage.chef.io/organizations/saymedia/global_permissions
//
// With all of that done, you can run like this:
//    make testacc TEST=./builtin/providers/chef

var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider

func init() {
	testAccProvider = Provider().(*schema.Provider)
	testAccProviders = map[string]terraform.ResourceProvider{
		"chef": testAccProvider,
	}
}

func TestProvider(t *testing.T) {
	if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestProvider_impl(t *testing.T) {
	var _ terraform.ResourceProvider = Provider()
}

func testAccPreCheck(t *testing.T) {
	if v := os.Getenv("CHEF_SERVER_URL"); v == "" {
		t.Fatal("CHEF_SERVER_URL must be set for acceptance tests")
	}
	if v := os.Getenv("CHEF_CLIENT_NAME"); v == "" {
		t.Fatal("CHEF_CLIENT_NAME must be set for acceptance tests")
	}
	if v := os.Getenv("CHEF_KEY_MATERIAL"); v == "" {
		t.Fatal("CHEF_KEY_MATERIAL must be set for acceptance tests")
	}
}
<commit_msg>provider/chef: Updating the provider_test comments to remove a company name (#10197)<commit_after>package chef

import (
	"os"
	"testing"

	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/terraform"
)

// To run these acceptance tests, you will need access to a Chef server.
// An easy way to get one is to sign up for a hosted Chef server account
// at https://manage.chef.io/signup , after which your base URL will
// be something like https://api.opscode.com/organizations/example/ .
// You will also need to create a "client" and write its private key to
// a file somewhere.
//
// You can then set the following environment variables to make these
// tests work:
// CHEF_SERVER_URL to the base URL as described above.
// CHEF_CLIENT_NAME to the name of the client object you created.
// CHEF_KEY_MATERIAL the key file contents.
//
// You will probably need to edit the global permissions on your Chef
// Server account to allow this client (or all clients, if you're lazy)
// to have both List and Create access on all types of object:
//     https://manage.chef.io/organizations/yourorg/global_permissions
//
// With all of that done, you can run like this:
//    make testacc TEST=./builtin/providers/chef

var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider

func init() {
	testAccProvider = Provider().(*schema.Provider)
	testAccProviders = map[string]terraform.ResourceProvider{
		"chef": testAccProvider,
	}
}

func TestProvider(t *testing.T) {
	if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestProvider_impl(t *testing.T) {
	var _ terraform.ResourceProvider = Provider()
}

func testAccPreCheck(t *testing.T) {
	if v := os.Getenv("CHEF_SERVER_URL"); v == "" {
		t.Fatal("CHEF_SERVER_URL must be set for acceptance tests")
	}
	if v := os.Getenv("CHEF_CLIENT_NAME"); v == "" {
		t.Fatal("CHEF_CLIENT_NAME must be set for acceptance tests")
	}
	if v := os.Getenv("CHEF_KEY_MATERIAL"); v == "" {
		t.Fatal("CHEF_KEY_MATERIAL must be set for acceptance tests")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"github.com/gokyle/goconfig"
	"github.com/gokyle/kludge/logsrv/logsrvc"
	"github.com/gokyle/uuid"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"
)

var (
	addr     string
	dbFile   string
	nodeID   string
	logger   *logsrvc.Logger
	tplDir   string
	assetDir string
)

func initLogging(cfgmap goconfig.ConfigMap) (regen bool) {
	var err error
	cfg, ok := cfgmap["logging"]
	if !ok {
		fmt.Println("no logging information!")
		os.Exit(1)
	}

	nodeID = cfg["node_id"]
	if nodeID == "" {
		nodeID, err = uuid.GenerateV4String()
		if err != nil {
			fmt.Println("failed to generate node ID:", err.Error())
			os.Exit(1)
		}
		regen = true
		cfgmap["logging"]["node_id"] = nodeID
	}
	logserver := cfg["loghost"]

	logger, err = logsrvc.Connect("logweb:"+nodeID, logserver)
	if err != nil {
		fmt.Println("failed to set up log host:", err.Error())
		os.Exit(1)
	}
	return
}

func initServer(cfgmap goconfig.ConfigMap) (regen bool) {
	var ok bool

	cfg, ok := cfgmap["server"]
	if !ok {
		logger.Fatal("bad config file: missing section 'server'")
	}

	if addr, ok = cfg["address"]; !ok {
		addr = ":8080"
	}

	if dbFile, ok = cfg["database"]; !ok {
		logger.Fatal("no database file configured")
	}

	if assetDir, ok = cfg["assets"]; !ok {
		assetDir = "assets"
	}

	if tplDir, ok = cfg["templates"]; !ok {
		tplDir = "templates"
	}
	return false
}

func init() {
	configFile := flag.String("f", "logwebrc",
		"path to configuration file")
	flag.Parse()

	cfg, err := goconfig.ParseFile(*configFile)
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}

	if initLogging(cfg) {
		updateConfig(cfg, *configFile)
	}

	if initServer(cfg) {
		updateConfig(cfg, *configFile)
	}
}

func templatePath(name string) string {
	return filepath.Join(tplDir, name)
}

func updateConfig(cfg goconfig.ConfigMap, cfgFile string) {
	err := cfg.WriteFile(cfgFile)
	if err != nil {
		fmt.Println("failed to update config file:",
			err.Error())
		os.Exit(1)
	}
}

func serverError(w http.ResponseWriter, err error) {
	logger.Println("error handling request:", err.Error())
	w.WriteHeader(http.StatusInternalServerError)
	w.Write([]byte(err.Error()))
}

func response(w http.ResponseWriter, r *http.Request) {
	logger.Printf("%s request from %s to %s", r.Method,
		r.RemoteAddr, r.URL.Path)

	var nodes []*responseTime
	nodelst, err := responseNodes()
	if err != nil {
		serverError(w, err)
		return
	}

	for _, nodeName := range nodelst {
		node, err := getNodeAverages(nodeName)
		if err != nil {
			serverError(w, err)
			return
		}
		nodes = append(nodes, node)
	}

	page, err := ioutil.ReadFile(templatePath("response.html"))
	if err != nil {
		serverError(w, err)
		return
	}
	respTpl := template.New("response")
	respTpl, err = respTpl.Parse(string(page))
	if err != nil {
		serverError(w, err)
		return
	}

	buf := new(bytes.Buffer)
	err = respTpl.Execute(w, nodes)
	if err != nil {
		serverError(w, err)
		return
	}
	w.Write(buf.Bytes())
}

func logs_all(w http.ResponseWriter, r *http.Request) {
	logger.Printf("%s request from %s to %s", r.Method,
		r.RemoteAddr, r.URL.Path)

	entries, err := logsFrom(0)
	if err != nil {
		serverError(w, err)
		return
	}

	if filepath.Ext(r.URL.Path) == ".txt" {
		dump_log(entries, w)
		return
	}

	page, err := ioutil.ReadFile(templatePath("logs.html"))
	if err != nil {
		serverError(w, err)
		return
	}

	tpl := template.New("logs")
	tpl, err = tpl.Parse(string(page))
	if err != nil {
		serverError(w, err)
		return
	}

	buf := new(bytes.Buffer)
	err = tpl.Execute(buf, entries)
	if err != nil {
		serverError(w, err)
		return
	}
	w.Write(buf.Bytes())
}

func dump_log(entries []*logEntry, w http.ResponseWriter) {
	for i := len(entries); i != 0; i++ {
		logLine := fmt.Sprintf("%s %s %s", entries[i].Time,
			entries[i].Node, entries[i].Msg)
		w.Write([]byte(logLine))
	}
}

func logs_hourly(w http.ResponseWriter, r *http.Request) {
	logger.Printf("%s request from %s to %s", r.Method,
		r.RemoteAddr, r.URL.Path)

	hour, err := time.ParseDuration("-1h")
	if err != nil {
		serverError(w, err)
		return
	}
	when := time.Now().Add(hour).Unix()

	entries, err := logsFrom(when)
	if err != nil {
		serverError(w, err)
		return
	}

	if filepath.Ext(r.URL.Path) == ".txt" {
		dump_log(entries, w)
		return
	}

	page, err := ioutil.ReadFile(templatePath("logs.html"))
	if err != nil {
		serverError(w, err)
		return
	}

	tpl := template.New("logs")
	tpl, err = tpl.Parse(string(page))
	if err != nil {
		serverError(w, err)
		return
	}

	buf := new(bytes.Buffer)
	err = tpl.Execute(buf, entries)
	if err != nil {
		serverError(w, err)
		return
	}
	w.Write(buf.Bytes())
}

func root(w http.ResponseWriter, r *http.Request) {
	logger.Printf("%s request from %s to %s", r.Method,
		r.RemoteAddr, r.URL.Path)

	indexFile, err := ioutil.ReadFile(templatePath("index.html"))
	if err != nil {
		serverError(w, err)
		return
	}

	respTpl := template.New("index")
	respTpl, err = respTpl.Parse(string(indexFile))
	if err != nil {
		serverError(w, err)
		return
	}

	buf := new(bytes.Buffer)
	err = respTpl.Execute(buf, nil)
	if err != nil {
		logger.Printf("buf: %s", string(buf.Bytes()))
		serverError(w, err)
		return
	}
	w.Write(buf.Bytes())
}

func main() {
	http.HandleFunc("/response/", response)
	http.HandleFunc("/logs/all", logs_all)
	http.HandleFunc("/logs/all.txt", logs_all)
	http.HandleFunc("/logs/hourly", logs_hourly)
	http.HandleFunc("/logs/hourly.txt", logs_hourly)
	http.HandleFunc("/", root)
	http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir(assetDir))))
	logger.Printf("logweb listing on http://%s/", addr)
	go http.ListenAndServe(addr, nil)
	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, os.Kill, os.Interrupt, syscall.SIGTERM)
	fmt.Println("[+] waiting for shutdown signal")
	<-sigc
	logger.Println("logweb shutdown")
}
<commit_msg>Add response time to all log dump<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"github.com/gokyle/goconfig"
	"github.com/gokyle/kludge/logsrv/logsrvc"
	"github.com/gokyle/uuid"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"
)

var (
	addr     string
	dbFile   string
	nodeID   string
	logger   *logsrvc.Logger
	tplDir   string
	assetDir string
)

func initLogging(cfgmap goconfig.ConfigMap) (regen bool) {
	var err error
	cfg, ok := cfgmap["logging"]
	if !ok {
		fmt.Println("no logging information!")
		os.Exit(1)
	}

	nodeID = cfg["node_id"]
	if nodeID == "" {
		nodeID, err = uuid.GenerateV4String()
		if err != nil {
			fmt.Println("failed to generate node ID:", err.Error())
			os.Exit(1)
		}
		regen = true
		cfgmap["logging"]["node_id"] = nodeID
	}
	logserver := cfg["loghost"]

	logger, err = logsrvc.Connect("logweb:"+nodeID, logserver)
	if err != nil {
		fmt.Println("failed to set up log host:", err.Error())
		os.Exit(1)
	}
	return
}

func initServer(cfgmap goconfig.ConfigMap) (regen bool) {
	var ok bool

	cfg, ok := cfgmap["server"]
	if !ok {
		logger.Fatal("bad config file: missing section 'server'")
	}

	if addr, ok = cfg["address"]; !ok {
		addr = ":8080"
	}

	if dbFile, ok = cfg["database"]; !ok {
		logger.Fatal("no database file configured")
	}

	if assetDir, ok = cfg["assets"]; !ok {
		assetDir = "assets"
	}

	if tplDir, ok = cfg["templates"]; !ok {
		tplDir = "templates"
	}
	return false
}

func init() {
	configFile := flag.String("f", "logwebrc",
		"path to configuration file")
	flag.Parse()

	cfg, err := goconfig.ParseFile(*configFile)
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}

	if initLogging(cfg) {
		updateConfig(cfg, *configFile)
	}

	if initServer(cfg) {
		updateConfig(cfg, *configFile)
	}
}

func templatePath(name string) string {
	return filepath.Join(tplDir, name)
}

func updateConfig(cfg goconfig.ConfigMap, cfgFile string) {
	err := cfg.WriteFile(cfgFile)
	if err != nil {
		fmt.Println("failed to update config file:",
			err.Error())
		os.Exit(1)
	}
}

func serverError(w http.ResponseWriter, err error) {
	logger.Println("error handling request:", err.Error())
	w.WriteHeader(http.StatusInternalServerError)
	w.Write([]byte(err.Error()))
}

func response(w http.ResponseWriter, r *http.Request) {
	logger.Printf("%s request from %s to %s", r.Method,
		r.RemoteAddr, r.URL.Path)

	var nodes []*responseTime
	nodelst, err := responseNodes()
	if err != nil {
		serverError(w, err)
		return
	}

	for _, nodeName := range nodelst {
		node, err := getNodeAverages(nodeName)
		if err != nil {
			serverError(w, err)
			return
		}
		nodes = append(nodes, node)
	}

	page, err := ioutil.ReadFile(templatePath("response.html"))
	if err != nil {
		serverError(w, err)
		return
	}
	respTpl := template.New("response")
	respTpl, err = respTpl.Parse(string(page))
	if err != nil {
		serverError(w, err)
		return
	}

	buf := new(bytes.Buffer)
	err = respTpl.Execute(w, nodes)
	if err != nil {
		serverError(w, err)
		return
	}
	w.Write(buf.Bytes())
}

func logs_all(w http.ResponseWriter, r *http.Request) {
	logger.Printf("%s request from %s to %s", r.Method,
		r.RemoteAddr, r.URL.Path)
	start := time.Now().UnixNano()

	entries, err := logsFrom(0)
	if err != nil {
		serverError(w, err)
		return
	}

	if filepath.Ext(r.URL.Path) == ".txt" {
		dump_log(entries, w)
		rtime := (time.Now().UnixNano() - start) / 1000.0
		logger.Printf("GET response time: %dus", rtime)
		return
	}

	page, err := ioutil.ReadFile(templatePath("logs.html"))
	if err != nil {
		serverError(w, err)
		return
	}

	tpl := template.New("logs")
	tpl, err = tpl.Parse(string(page))
	if err != nil {
		serverError(w, err)
		return
	}

	buf := new(bytes.Buffer)
	err = tpl.Execute(buf, entries)
	if err != nil {
		serverError(w, err)
		return
	}
	w.Write(buf.Bytes())
	rtime := (time.Now().UnixNano() - start) / 1000.0
	logger.Printf("GET response time: %dus", rtime)
}

func dump_log(entries []*logEntry, w http.ResponseWriter) {
	logger.Printf("dumping %d entries to text file", len(entries))
	w.Header().Add("content-type", "text-plain")
	for i := len(entries); i != 0; i++ {
		logLine := fmt.Sprintf("%s %s %s", entries[i].Time,
			entries[i].Node, entries[i].Msg)
		w.Write([]byte(logLine))
	}
}

func logs_hourly(w http.ResponseWriter, r *http.Request) {
	logger.Printf("%s request from %s to %s", r.Method,
		r.RemoteAddr, r.URL.Path)

	hour, err := time.ParseDuration("-1h")
	if err != nil {
		serverError(w, err)
		return
	}
	when := time.Now().Add(hour).Unix()

	entries, err := logsFrom(when)
	if err != nil {
		serverError(w, err)
		return
	}

	if filepath.Ext(r.URL.Path) == ".txt" {
		dump_log(entries, w)
		return
	}

	page, err := ioutil.ReadFile(templatePath("logs.html"))
	if err != nil {
		serverError(w, err)
		return
	}

	tpl := template.New("logs")
	tpl, err = tpl.Parse(string(page))
	if err != nil {
		serverError(w, err)
		return
	}

	buf := new(bytes.Buffer)
	err = tpl.Execute(buf, entries)
	if err != nil {
		serverError(w, err)
		return
	}
	w.Write(buf.Bytes())
}

func root(w http.ResponseWriter, r *http.Request) {
	logger.Printf("%s request from %s to %s", r.Method,
		r.RemoteAddr, r.URL.Path)

	indexFile, err := ioutil.ReadFile(templatePath("index.html"))
	if err != nil {
		serverError(w, err)
		return
	}

	respTpl := template.New("index")
	respTpl, err = respTpl.Parse(string(indexFile))
	if err != nil {
		serverError(w, err)
		return
	}

	buf := new(bytes.Buffer)
	err = respTpl.Execute(buf, nil)
	if err != nil {
		logger.Printf("buf: %s", string(buf.Bytes()))
		serverError(w, err)
		return
	}
	w.Write(buf.Bytes())
}

func main() {
	http.HandleFunc("/response/", response)
	http.HandleFunc("/logs/all", logs_all)
	http.HandleFunc("/logs/all.txt", logs_all)
	http.HandleFunc("/logs/hourly", logs_hourly)
	http.HandleFunc("/logs/hourly.txt", logs_hourly)
	http.HandleFunc("/", root)
	http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir(assetDir))))
	logger.Printf("logweb listing on http://%s/", addr)
	go http.ListenAndServe(addr, nil)
	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, os.Kill, os.Interrupt, syscall.SIGTERM)
	fmt.Println("[+] waiting for shutdown signal")
	<-sigc
	logger.Println("logweb shutdown")
}
<|endoftext|>


<commit_before>package nsq

import (
	"net"
	"strconv"
)

type LookupPeer struct {
	*ProtocolClient
}

func NewLookupPeer(tcpAddr *net.TCPAddr) *LookupPeer {
	return &LookupPeer{&ProtocolClient{tcpAddr: tcpAddr}}
}

func (c *LookupPeer) Announce(topic string, port int) *ProtocolCommand {
	var params = [][]byte{[]byte(topic), []byte(strconv.Itoa(port))}
	return &ProtocolCommand{[]byte("ANNOUNCE"), params}
}

func (c *LookupPeer) Ping() *ProtocolCommand {
	return &ProtocolCommand{[]byte("PING"), make([][]byte, 0)}
}
<commit_msg>announce channels to nsqlookupd<commit_after>package nsq

import (
	"net"
	"strconv"
)

type LookupPeer struct {
	*ProtocolClient
}

func NewLookupPeer(tcpAddr *net.TCPAddr) *LookupPeer {
	return &LookupPeer{&ProtocolClient{tcpAddr: tcpAddr}}
}

func (c *LookupPeer) Announce(topic string, channel string, port int) *ProtocolCommand {
	var params = [][]byte{[]byte(topic), []byte(channel), []byte(strconv.Itoa(port))}
	return &ProtocolCommand{[]byte("ANNOUNCE"), params}
}

func (c *LookupPeer) Ping() *ProtocolCommand {
	return &ProtocolCommand{[]byte("PING"), make([][]byte, 0)}
}
<|endoftext|>


<commit_before>/* Copyright (c) 2016 Jason Ish
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package elasticsearch

// Response to an Elastic Search ping (GET /)
type PingResponse struct {
	Name        string `json:"name"`
	ClusterName string `json:"cluster_name"`
	Version     struct {
		Number string `json:"number"`
	} `json:"version"`
	Tagline string `json:"tagline"`
}

type BulkCreateHeader struct {
	Create struct {
		Index string `json:"_index"`
		Type  string `json:"_type"`
		Id    string `json:"_id"`
	} `json:"create"`
}

// Struct representing a response to a _bulk request.
type BulkResponse struct {
	Took   uint64                   `json:"took"`
	Errors bool                     `json:"errors"`
	Items  []map[string]interface{} `json:"items"`
}

type Hits struct {
	Total uint64                   `json:"total"`
	Hits  []map[string]interface{} `json:"hits"`
}

type SearchResponse struct {
	Shards   map[string]interface{} `json:"_shards"`
	TimedOut bool                   `json:"timed_out"`
	Took     uint64                 `json:"took"`
	Hits     Hits                   `json:"hits"`
	ScrollId string                 `json:"_scroll_id"`
}
<commit_msg>es: function to get ES major version<commit_after>/* Copyright (c) 2016 Jason Ish
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package elasticsearch

import (
	"strings"
	"strconv"
)

// Response to an Elastic Search ping (GET /)
type PingResponse struct {
	Name        string `json:"name"`
	ClusterName string `json:"cluster_name"`
	Version     struct {
		Number string `json:"number"`
	} `json:"version"`
	Tagline string `json:"tagline"`
}

func (p PingResponse) MajorVersion() (int64) {
	version := p.Version.Number
	parts := strings.Split(version, ".")
	major, err := strconv.ParseInt(parts[0], 10, 64)
	if err != nil {
		return -1
	} else {
		return major
	}
}

type BulkCreateHeader struct {
	Create struct {
		Index string `json:"_index"`
		Type  string `json:"_type"`
		Id    string `json:"_id"`
	} `json:"create"`
}

// Struct representing a response to a _bulk request.
type BulkResponse struct {
	Took   uint64                   `json:"took"`
	Errors bool                     `json:"errors"`
	Items  []map[string]interface{} `json:"items"`
}

type Hits struct {
	Total uint64                   `json:"total"`
	Hits  []map[string]interface{} `json:"hits"`
}

type SearchResponse struct {
	Shards   map[string]interface{} `json:"_shards"`
	TimedOut bool                   `json:"timed_out"`
	Took     uint64                 `json:"took"`
	Hits     Hits                   `json:"hits"`
	ScrollId string                 `json:"_scroll_id"`
}
<|endoftext|>


<commit_before>package policy

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"math/rand"
	"net"
	"regexp"
	"strings"
	"sync"

	"github.com/weppos/publicsuffix-go/publicsuffix"
	"golang.org/x/net/idna"

	"github.com/letsencrypt/boulder/core"
	berrors "github.com/letsencrypt/boulder/errors"
	"github.com/letsencrypt/boulder/features"
	blog "github.com/letsencrypt/boulder/log"
	"github.com/letsencrypt/boulder/reloader"
)

// AuthorityImpl enforces CA policy decisions.
type AuthorityImpl struct {
	log blog.Logger

	blacklist      map[string]bool
	exactBlacklist map[string]bool
	blacklistMu    sync.RWMutex

	enabledChallenges map[string]bool
	pseudoRNG         *rand.Rand
}

// New constructs a Policy Authority.
// TODO(https://github.com/letsencrypt/boulder/issues/1616): Remove the _ bool
// argument (used to be enforceWhitelist). Update all callers.
func New(challengeTypes map[string]bool) (*AuthorityImpl, error) {

	pa := AuthorityImpl{
		log:               blog.Get(),
		enabledChallenges: challengeTypes,
		// We don't need real randomness for this.
		pseudoRNG: rand.New(rand.NewSource(99)),
	}

	return &pa, nil
}

type blacklistJSON struct {
	Blacklist      []string
	ExactBlacklist []string
}

// SetHostnamePolicyFile will load the given policy file, returning error if it
// fails. It will also start a reloader in case the file changes.
func (pa *AuthorityImpl) SetHostnamePolicyFile(f string) error {
	_, err := reloader.New(f, pa.loadHostnamePolicy, pa.hostnamePolicyLoadError)
	return err
}

func (pa *AuthorityImpl) hostnamePolicyLoadError(err error) {
	pa.log.AuditErr(fmt.Sprintf("error loading hostname policy: %s", err))
}

func (pa *AuthorityImpl) loadHostnamePolicy(b []byte) error {
	hash := sha256.Sum256(b)
	pa.log.Info(fmt.Sprintf("loading hostname policy, sha256: %s",
		hex.EncodeToString(hash[:])))
	var bl blacklistJSON
	err := json.Unmarshal(b, &bl)
	if err != nil {
		return err
	}
	if len(bl.Blacklist) == 0 {
		return fmt.Errorf("No entries in blacklist.")
	}
	nameMap := make(map[string]bool)
	for _, v := range bl.Blacklist {
		nameMap[v] = true
	}
	exactNameMap := make(map[string]bool)
	for _, v := range bl.ExactBlacklist {
		exactNameMap[v] = true
	}
	pa.blacklistMu.Lock()
	pa.blacklist = nameMap
	pa.exactBlacklist = exactNameMap
	pa.blacklistMu.Unlock()
	return nil
}

const (
	maxLabels = 10

	// RFC 1034 says DNS labels have a max of 63 octets, and names have a max of 255
	// octets: https://tools.ietf.org/html/rfc1035#page-10. Since two of those octets
	// are taken up by the leading length byte and the trailing root period the actual
	// max length becomes 253.
	maxLabelLength         = 63
	maxDNSIdentifierLength = 253
)

var dnsLabelRegexp = regexp.MustCompile("^[a-z0-9][a-z0-9-]{0,62}$")
var punycodeRegexp = regexp.MustCompile("^xn--")

func isDNSCharacter(ch byte) bool {
	return ('a' <= ch && ch <= 'z') ||
		('A' <= ch && ch <= 'Z') ||
		('0' <= ch && ch <= '9') ||
		ch == '.' || ch == '-'
}

// Test whether the domain name indicated by the label set is a label-wise
// suffix match for the provided suffix set.  If the `properSuffix` flag is
// set, then the name is required to not be in the suffix set (i.e., it must
// have at least one label beyond any suffix in the set).
func suffixMatch(labels []string, suffixSet map[string]bool, properSuffix bool) bool {
	for i := range labels {
		if domain := strings.Join(labels[i:], "."); suffixSet[domain] {
			// If we match on the whole domain, gate on properSuffix
			return !properSuffix || (i > 0)
		}
	}
	return false
}

var (
	errInvalidIdentifier   = berrors.MalformedError("Invalid identifier type")
	errNonPublic           = berrors.MalformedError("Name does not end in a public suffix")
	errICANNTLD            = berrors.MalformedError("Name is an ICANN TLD")
	errBlacklisted         = berrors.RejectedIdentifierError("Policy forbids issuing for name")
	errNotWhitelisted      = berrors.MalformedError("Name is not whitelisted")
	errInvalidDNSCharacter = berrors.MalformedError("Invalid character in DNS name")
	errNameTooLong         = berrors.MalformedError("DNS name too long")
	errIPAddress           = berrors.MalformedError("Issuance for IP addresses not supported")
	errTooManyLabels       = berrors.MalformedError("DNS name has too many labels")
	errEmptyName           = berrors.MalformedError("DNS name was empty")
	errNameEndsInDot       = berrors.MalformedError("DNS name ends in a period")
	errTooFewLabels        = berrors.MalformedError("DNS name does not have enough labels")
	errLabelTooShort       = berrors.MalformedError("DNS label is too short")
	errLabelTooLong        = berrors.MalformedError("DNS label is too long")
	// TODO(@cpu): Delete `errIDNNotSupported` when IDNASupport feature flag is removed.
	errIDNNotSupported = berrors.MalformedError("Internationalized domain names (starting with xn--) not yet supported")
	errMalformedIDN    = berrors.MalformedError("DNS label contains malformed punycode")
)

// WillingToIssue determines whether the CA is willing to issue for the provided
// identifier. It expects domains in id to be lowercase to prevent mismatched
// cases breaking queries.
//
// We place several criteria on identifiers we are willing to issue for:
//
//  * MUST self-identify as DNS identifiers
//  * MUST contain only bytes in the DNS hostname character set
//  * MUST NOT have more than maxLabels labels
//  * MUST follow the DNS hostname syntax rules in RFC 1035 and RFC 2181
//    In particular:
//    * MUST NOT contain underscores
//  * MUST NOT contain IDN labels (xn--)
//  * MUST NOT match the syntax of an IP address
//  * MUST end in a public suffix
//  * MUST have at least one label in addition to the public suffix
//  * MUST NOT be a label-wise suffix match for a name on the black list,
//    where comparison is case-independent (normalized to lower case)
//
// If WillingToIssue returns an error, it will be of type MalformedRequestError.
func (pa *AuthorityImpl) WillingToIssue(id core.AcmeIdentifier) error {
	if id.Type != core.IdentifierDNS {
		return errInvalidIdentifier
	}
	domain := id.Value

	if domain == "" {
		return errEmptyName
	}

	for _, ch := range []byte(domain) {
		if !isDNSCharacter(ch) {
			return errInvalidDNSCharacter
		}
	}

	if len(domain) > maxDNSIdentifierLength {
		return errNameTooLong
	}

	if ip := net.ParseIP(domain); ip != nil {
		return errIPAddress
	}

	if strings.HasSuffix(domain, ".") {
		return errNameEndsInDot
	}

	labels := strings.Split(domain, ".")
	if len(labels) > maxLabels {
		return errTooManyLabels
	}
	if len(labels) < 2 {
		return errTooFewLabels
	}
	for _, label := range labels {
		if len(label) < 1 {
			return errLabelTooShort
		}
		if len(label) > maxLabelLength {
			return errLabelTooLong
		}

		if !dnsLabelRegexp.MatchString(label) {
			return errInvalidDNSCharacter
		}

		if label[len(label)-1] == '-' {
			return errInvalidDNSCharacter
		}

		if punycodeRegexp.MatchString(label) {
			if features.Enabled(features.IDNASupport) {
				// We don't care about script usage, if a name is resolvable it was
				// registered with a higher power and they should be enforcing their
				// own policy. As long as it was properly encoded that is enough
				// for us.
				_, err := idna.ToUnicode(label)
				if err != nil {
					return errMalformedIDN
				}
			} else {
				return errIDNNotSupported
			}
		}
	}

	// Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD.
	icannTLD, err := extractDomainIANASuffix(domain)
	if err != nil {
		return errNonPublic
	}
	if icannTLD == domain {
		return errICANNTLD
	}

	// Require no match against blacklist
	if err := pa.checkHostLists(domain); err != nil {
		return err
	}

	return nil
}

func (pa *AuthorityImpl) checkHostLists(domain string) error {
	pa.blacklistMu.RLock()
	defer pa.blacklistMu.RUnlock()

	if pa.blacklist == nil {
		return fmt.Errorf("Hostname policy not yet loaded.")
	}

	labels := strings.Split(domain, ".")
	for i := range labels {
		joined := strings.Join(labels[i:], ".")
		if pa.blacklist[joined] {
			return errBlacklisted
		}
	}

	if pa.exactBlacklist[domain] {
		return errBlacklisted
	}
	return nil
}

// ChallengesFor makes a decision of what challenges, and combinations, are
// acceptable for the given identifier.
//
// Note: Current implementation is static, but future versions may not be.
func (pa *AuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier) ([]core.Challenge, [][]int) {
	challenges := []core.Challenge{}

	if pa.enabledChallenges[core.ChallengeTypeHTTP01] {
		challenges = append(challenges, core.HTTPChallenge01())
	}

	if pa.enabledChallenges[core.ChallengeTypeTLSSNI01] {
		challenges = append(challenges, core.TLSSNIChallenge01())
	}

	if features.Enabled(features.AllowTLS02Challenges) && pa.enabledChallenges[core.ChallengeTypeTLSSNI02] {
		challenges = append(challenges, core.TLSSNIChallenge02())
	}

	if pa.enabledChallenges[core.ChallengeTypeDNS01] {
		challenges = append(challenges, core.DNSChallenge01())
	}

	// We shuffle the challenges and combinations to prevent ACME clients from
	// relying on the specific order that boulder returns them in.
	shuffled := make([]core.Challenge, len(challenges))
	combinations := make([][]int, len(challenges))

	for i, challIdx := range pa.pseudoRNG.Perm(len(challenges)) {
		shuffled[i] = challenges[challIdx]
		combinations[i] = []int{i}
	}

	shuffledCombos := make([][]int, len(combinations))
	for i, comboIdx := range pa.pseudoRNG.Perm(len(combinations)) {
		shuffledCombos[i] = combinations[comboIdx]
	}

	return shuffled, shuffledCombos
}

// ExtractDomainIANASuffix returns the public suffix of the domain using only the "ICANN"
// section of the Public Suffix List database.
// If the domain does not end in a suffix that belongs to an IANA-assigned
// domain, ExtractDomainIANASuffix returns an error.
func extractDomainIANASuffix(name string) (string, error) {
	if name == "" {
		return "", fmt.Errorf("Blank name argument passed to ExtractDomainIANASuffix")
	}

	rule := publicsuffix.DefaultList.Find(name, &publicsuffix.FindOptions{IgnorePrivate: true, DefaultRule: nil})
	if rule == nil {
		return "", fmt.Errorf("Domain %s has no IANA TLD", name)
	}

	suffix := rule.Decompose(name)[1]

	// If the TLD is empty, it means name is actually a suffix.
	// In fact, decompose returns an array of empty strings in this case.
	if suffix == "" {
		suffix = name
	}

	return suffix, nil
}
<commit_msg>Removes outdated comment & unused errNotWhitelisted. (#2744)<commit_after>package policy

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"math/rand"
	"net"
	"regexp"
	"strings"
	"sync"

	"github.com/weppos/publicsuffix-go/publicsuffix"
	"golang.org/x/net/idna"

	"github.com/letsencrypt/boulder/core"
	berrors "github.com/letsencrypt/boulder/errors"
	"github.com/letsencrypt/boulder/features"
	blog "github.com/letsencrypt/boulder/log"
	"github.com/letsencrypt/boulder/reloader"
)

// AuthorityImpl enforces CA policy decisions.
type AuthorityImpl struct {
	log blog.Logger

	blacklist      map[string]bool
	exactBlacklist map[string]bool
	blacklistMu    sync.RWMutex

	enabledChallenges map[string]bool
	pseudoRNG         *rand.Rand
}

// New constructs a Policy Authority.
func New(challengeTypes map[string]bool) (*AuthorityImpl, error) {

	pa := AuthorityImpl{
		log:               blog.Get(),
		enabledChallenges: challengeTypes,
		// We don't need real randomness for this.
		pseudoRNG: rand.New(rand.NewSource(99)),
	}

	return &pa, nil
}

type blacklistJSON struct {
	Blacklist      []string
	ExactBlacklist []string
}

// SetHostnamePolicyFile will load the given policy file, returning error if it
// fails. It will also start a reloader in case the file changes.
func (pa *AuthorityImpl) SetHostnamePolicyFile(f string) error {
	_, err := reloader.New(f, pa.loadHostnamePolicy, pa.hostnamePolicyLoadError)
	return err
}

func (pa *AuthorityImpl) hostnamePolicyLoadError(err error) {
	pa.log.AuditErr(fmt.Sprintf("error loading hostname policy: %s", err))
}

func (pa *AuthorityImpl) loadHostnamePolicy(b []byte) error {
	hash := sha256.Sum256(b)
	pa.log.Info(fmt.Sprintf("loading hostname policy, sha256: %s",
		hex.EncodeToString(hash[:])))
	var bl blacklistJSON
	err := json.Unmarshal(b, &bl)
	if err != nil {
		return err
	}
	if len(bl.Blacklist) == 0 {
		return fmt.Errorf("No entries in blacklist.")
	}
	nameMap := make(map[string]bool)
	for _, v := range bl.Blacklist {
		nameMap[v] = true
	}
	exactNameMap := make(map[string]bool)
	for _, v := range bl.ExactBlacklist {
		exactNameMap[v] = true
	}
	pa.blacklistMu.Lock()
	pa.blacklist = nameMap
	pa.exactBlacklist = exactNameMap
	pa.blacklistMu.Unlock()
	return nil
}

const (
	maxLabels = 10

	// RFC 1034 says DNS labels have a max of 63 octets, and names have a max of 255
	// octets: https://tools.ietf.org/html/rfc1035#page-10. Since two of those octets
	// are taken up by the leading length byte and the trailing root period the actual
	// max length becomes 253.
	maxLabelLength         = 63
	maxDNSIdentifierLength = 253
)

var dnsLabelRegexp = regexp.MustCompile("^[a-z0-9][a-z0-9-]{0,62}$")
var punycodeRegexp = regexp.MustCompile("^xn--")

func isDNSCharacter(ch byte) bool {
	return ('a' <= ch && ch <= 'z') ||
		('A' <= ch && ch <= 'Z') ||
		('0' <= ch && ch <= '9') ||
		ch == '.' || ch == '-'
}

// Test whether the domain name indicated by the label set is a label-wise
// suffix match for the provided suffix set.  If the `properSuffix` flag is
// set, then the name is required to not be in the suffix set (i.e., it must
// have at least one label beyond any suffix in the set).
func suffixMatch(labels []string, suffixSet map[string]bool, properSuffix bool) bool {
	for i := range labels {
		if domain := strings.Join(labels[i:], "."); suffixSet[domain] {
			// If we match on the whole domain, gate on properSuffix
			return !properSuffix || (i > 0)
		}
	}
	return false
}

var (
	errInvalidIdentifier   = berrors.MalformedError("Invalid identifier type")
	errNonPublic           = berrors.MalformedError("Name does not end in a public suffix")
	errICANNTLD            = berrors.MalformedError("Name is an ICANN TLD")
	errBlacklisted         = berrors.RejectedIdentifierError("Policy forbids issuing for name")
	errInvalidDNSCharacter = berrors.MalformedError("Invalid character in DNS name")
	errNameTooLong         = berrors.MalformedError("DNS name too long")
	errIPAddress           = berrors.MalformedError("Issuance for IP addresses not supported")
	errTooManyLabels       = berrors.MalformedError("DNS name has too many labels")
	errEmptyName           = berrors.MalformedError("DNS name was empty")
	errNameEndsInDot       = berrors.MalformedError("DNS name ends in a period")
	errTooFewLabels        = berrors.MalformedError("DNS name does not have enough labels")
	errLabelTooShort       = berrors.MalformedError("DNS label is too short")
	errLabelTooLong        = berrors.MalformedError("DNS label is too long")
	// TODO(@cpu): Delete `errIDNNotSupported` when IDNASupport feature flag is removed.
	errIDNNotSupported = berrors.MalformedError("Internationalized domain names (starting with xn--) not yet supported")
	errMalformedIDN    = berrors.MalformedError("DNS label contains malformed punycode")
)

// WillingToIssue determines whether the CA is willing to issue for the provided
// identifier. It expects domains in id to be lowercase to prevent mismatched
// cases breaking queries.
//
// We place several criteria on identifiers we are willing to issue for:
//
//  * MUST self-identify as DNS identifiers
//  * MUST contain only bytes in the DNS hostname character set
//  * MUST NOT have more than maxLabels labels
//  * MUST follow the DNS hostname syntax rules in RFC 1035 and RFC 2181
//    In particular:
//    * MUST NOT contain underscores
//  * MUST NOT contain IDN labels (xn--)
//  * MUST NOT match the syntax of an IP address
//  * MUST end in a public suffix
//  * MUST have at least one label in addition to the public suffix
//  * MUST NOT be a label-wise suffix match for a name on the black list,
//    where comparison is case-independent (normalized to lower case)
//
// If WillingToIssue returns an error, it will be of type MalformedRequestError.
func (pa *AuthorityImpl) WillingToIssue(id core.AcmeIdentifier) error {
	if id.Type != core.IdentifierDNS {
		return errInvalidIdentifier
	}
	domain := id.Value

	if domain == "" {
		return errEmptyName
	}

	for _, ch := range []byte(domain) {
		if !isDNSCharacter(ch) {
			return errInvalidDNSCharacter
		}
	}

	if len(domain) > maxDNSIdentifierLength {
		return errNameTooLong
	}

	if ip := net.ParseIP(domain); ip != nil {
		return errIPAddress
	}

	if strings.HasSuffix(domain, ".") {
		return errNameEndsInDot
	}

	labels := strings.Split(domain, ".")
	if len(labels) > maxLabels {
		return errTooManyLabels
	}
	if len(labels) < 2 {
		return errTooFewLabels
	}
	for _, label := range labels {
		if len(label) < 1 {
			return errLabelTooShort
		}
		if len(label) > maxLabelLength {
			return errLabelTooLong
		}

		if !dnsLabelRegexp.MatchString(label) {
			return errInvalidDNSCharacter
		}

		if label[len(label)-1] == '-' {
			return errInvalidDNSCharacter
		}

		if punycodeRegexp.MatchString(label) {
			if features.Enabled(features.IDNASupport) {
				// We don't care about script usage, if a name is resolvable it was
				// registered with a higher power and they should be enforcing their
				// own policy. As long as it was properly encoded that is enough
				// for us.
				_, err := idna.ToUnicode(label)
				if err != nil {
					return errMalformedIDN
				}
			} else {
				return errIDNNotSupported
			}
		}
	}

	// Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD.
	icannTLD, err := extractDomainIANASuffix(domain)
	if err != nil {
		return errNonPublic
	}
	if icannTLD == domain {
		return errICANNTLD
	}

	// Require no match against blacklist
	if err := pa.checkHostLists(domain); err != nil {
		return err
	}

	return nil
}

func (pa *AuthorityImpl) checkHostLists(domain string) error {
	pa.blacklistMu.RLock()
	defer pa.blacklistMu.RUnlock()

	if pa.blacklist == nil {
		return fmt.Errorf("Hostname policy not yet loaded.")
	}

	labels := strings.Split(domain, ".")
	for i := range labels {
		joined := strings.Join(labels[i:], ".")
		if pa.blacklist[joined] {
			return errBlacklisted
		}
	}

	if pa.exactBlacklist[domain] {
		return errBlacklisted
	}
	return nil
}

// ChallengesFor makes a decision of what challenges, and combinations, are
// acceptable for the given identifier.
//
// Note: Current implementation is static, but future versions may not be.
func (pa *AuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier) ([]core.Challenge, [][]int) {
	challenges := []core.Challenge{}

	if pa.enabledChallenges[core.ChallengeTypeHTTP01] {
		challenges = append(challenges, core.HTTPChallenge01())
	}

	if pa.enabledChallenges[core.ChallengeTypeTLSSNI01] {
		challenges = append(challenges, core.TLSSNIChallenge01())
	}

	if features.Enabled(features.AllowTLS02Challenges) && pa.enabledChallenges[core.ChallengeTypeTLSSNI02] {
		challenges = append(challenges, core.TLSSNIChallenge02())
	}

	if pa.enabledChallenges[core.ChallengeTypeDNS01] {
		challenges = append(challenges, core.DNSChallenge01())
	}

	// We shuffle the challenges and combinations to prevent ACME clients from
	// relying on the specific order that boulder returns them in.
	shuffled := make([]core.Challenge, len(challenges))
	combinations := make([][]int, len(challenges))

	for i, challIdx := range pa.pseudoRNG.Perm(len(challenges)) {
		shuffled[i] = challenges[challIdx]
		combinations[i] = []int{i}
	}

	shuffledCombos := make([][]int, len(combinations))
	for i, comboIdx := range pa.pseudoRNG.Perm(len(combinations)) {
		shuffledCombos[i] = combinations[comboIdx]
	}

	return shuffled, shuffledCombos
}

// ExtractDomainIANASuffix returns the public suffix of the domain using only the "ICANN"
// section of the Public Suffix List database.
// If the domain does not end in a suffix that belongs to an IANA-assigned
// domain, ExtractDomainIANASuffix returns an error.
func extractDomainIANASuffix(name string) (string, error) {
	if name == "" {
		return "", fmt.Errorf("Blank name argument passed to ExtractDomainIANASuffix")
	}

	rule := publicsuffix.DefaultList.Find(name, &publicsuffix.FindOptions{IgnorePrivate: true, DefaultRule: nil})
	if rule == nil {
		return "", fmt.Errorf("Domain %s has no IANA TLD", name)
	}

	suffix := rule.Decompose(name)[1]

	// If the TLD is empty, it means name is actually a suffix.
	// In fact, decompose returns an array of empty strings in this case.
	if suffix == "" {
		suffix = name
	}

	return suffix, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 fuseutil

import (
	"github.com/jacobsa/fuse"
	"golang.org/x/net/context"
)

// Embed this within your file system type to inherit default implementations
// of all methods that return fuse.ENOSYS.
type NotImplementedFileSystem struct {
}

var _ fuse.FileSystem = &NotImplementedFileSystem{}

func (fs *NotImplementedFileSystem) Init(
	ctx context.Context,
	req *fuse.InitRequest) (*fuse.InitResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) LookUpInode(
	ctx context.Context,
	req *fuse.LookUpInodeRequest) (*fuse.LookUpInodeResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) GetInodeAttributes(
	ctx context.Context,
	req *fuse.GetInodeAttributesRequest) (
	*fuse.GetInodeAttributesResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) SetInodeAttributes(
	ctx context.Context,
	req *fuse.SetInodeAttributesRequest) (
	*fuse.SetInodeAttributesResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) ForgetInode(
	ctx context.Context,
	req *fuse.ForgetInodeRequest) (*fuse.ForgetInodeResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) MkDir(
	ctx context.Context,
	req *fuse.MkDirRequest) (*fuse.MkDirResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) CreateFile(
	ctx context.Context,
	req *fuse.CreateFileRequest) (*fuse.CreateFileResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) RmDir(
	ctx context.Context,
	req *fuse.RmDirRequest) (*fuse.RmDirResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) Unlink(
	ctx context.Context,
	req *fuse.UnlinkRequest) (*fuse.UnlinkResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) OpenDir(
	ctx context.Context,
	req *fuse.OpenDirRequest) (*fuse.OpenDirResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) ReadDir(
	ctx context.Context,
	req *fuse.ReadDirRequest) (*fuse.ReadDirResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) ReleaseDirHandle(
	ctx context.Context,
	req *fuse.ReleaseDirHandleRequest) (*fuse.ReleaseDirHandleResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) OpenFile(
	ctx context.Context,
	req *fuse.OpenFileRequest) (*fuse.OpenFileResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) ReadFile(
	ctx context.Context,
	req *fuse.ReadFileRequest) (*fuse.ReadFileResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) WriteFile(
	ctx context.Context,
	req *fuse.WriteFileRequest) (*fuse.WriteFileResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) FlushFile(
	ctx context.Context,
	req *fuse.FlushFileRequest) (*fuse.FlushFileResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) ReleaseFileHandle(
	ctx context.Context,
	req *fuse.ReleaseFileHandleRequest) (*fuse.ReleaseFileHandleResponse, error) {
	return nil, fuse.ENOSYS
}
<commit_msg>NotImplementedFileSystem.SyncFile<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 fuseutil

import (
	"github.com/jacobsa/fuse"
	"golang.org/x/net/context"
)

// Embed this within your file system type to inherit default implementations
// of all methods that return fuse.ENOSYS.
type NotImplementedFileSystem struct {
}

var _ fuse.FileSystem = &NotImplementedFileSystem{}

func (fs *NotImplementedFileSystem) Init(
	ctx context.Context,
	req *fuse.InitRequest) (*fuse.InitResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) LookUpInode(
	ctx context.Context,
	req *fuse.LookUpInodeRequest) (*fuse.LookUpInodeResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) GetInodeAttributes(
	ctx context.Context,
	req *fuse.GetInodeAttributesRequest) (
	*fuse.GetInodeAttributesResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) SetInodeAttributes(
	ctx context.Context,
	req *fuse.SetInodeAttributesRequest) (
	*fuse.SetInodeAttributesResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) ForgetInode(
	ctx context.Context,
	req *fuse.ForgetInodeRequest) (*fuse.ForgetInodeResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) MkDir(
	ctx context.Context,
	req *fuse.MkDirRequest) (*fuse.MkDirResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) CreateFile(
	ctx context.Context,
	req *fuse.CreateFileRequest) (*fuse.CreateFileResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) RmDir(
	ctx context.Context,
	req *fuse.RmDirRequest) (*fuse.RmDirResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) Unlink(
	ctx context.Context,
	req *fuse.UnlinkRequest) (*fuse.UnlinkResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) OpenDir(
	ctx context.Context,
	req *fuse.OpenDirRequest) (*fuse.OpenDirResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) ReadDir(
	ctx context.Context,
	req *fuse.ReadDirRequest) (*fuse.ReadDirResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) ReleaseDirHandle(
	ctx context.Context,
	req *fuse.ReleaseDirHandleRequest) (*fuse.ReleaseDirHandleResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) OpenFile(
	ctx context.Context,
	req *fuse.OpenFileRequest) (*fuse.OpenFileResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) ReadFile(
	ctx context.Context,
	req *fuse.ReadFileRequest) (*fuse.ReadFileResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) WriteFile(
	ctx context.Context,
	req *fuse.WriteFileRequest) (*fuse.WriteFileResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) SyncFile(
	ctx context.Context,
	req *fuse.SyncFileRequest) (*fuse.SyncFileResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) FlushFile(
	ctx context.Context,
	req *fuse.FlushFileRequest) (*fuse.FlushFileResponse, error) {
	return nil, fuse.ENOSYS
}

func (fs *NotImplementedFileSystem) ReleaseFileHandle(
	ctx context.Context,
	req *fuse.ReleaseFileHandleRequest) (*fuse.ReleaseFileHandleResponse, error) {
	return nil, fuse.ENOSYS
}
<|endoftext|>


<commit_before>package core

import (
	"flag"
	"os"
	"reflect"
	"strings"
	"testing"

	"github.com/joyent/containerpilot/commands"
)

// ------------------------------------------

var testJSON = `{
	"consul": "consul:8500",
	"preStart": "/bin/to/preStart.sh arg1 arg2",
	"preStop": ["/bin/to/preStop.sh","arg1","arg2"],
	"postStop": ["/bin/to/postStop.sh"],
	"stopTimeout": 5,
	"services": [
			{
					"name": "serviceA",
					"port": 8080,
					"interfaces": "eth0",
					"health": "/bin/to/healthcheck/for/service/A.sh",
					"poll": 30,
					"ttl": "19",
					"tags": ["tag1","tag2"]
			},
			{
					"name": "serviceB",
					"port": 5000,
					"interfaces": ["ethwe","eth0"],
					"health": "/bin/to/healthcheck/for/service/B.sh",
					"poll": 30,
					"ttl": 103
			}
	],
	"backends": [
			{
					"name": "upstreamA",
					"poll": 11,
					"onChange": "/bin/to/onChangeEvent/for/upstream/A.sh {{.TEST}}",
					"tag": "dev"
			},
			{
					"name": "upstreamB",
					"poll": 79,
					"onChange": "/bin/to/onChangeEvent/for/upstream/B.sh {{.ENV_NOT_FOUND}}"
			}
	]
}
`

func TestValidConfigParse(t *testing.T) {
	defer argTestCleanup(argTestSetup())

	os.Setenv("TEST", "HELLO")
	os.Args = []string{"this", "-config", testJSON, "/testdata/test.sh", "valid1", "--debug"}
	app, err := LoadApp()
	if err != nil {
		t.Fatalf("Unexpected error in LoadApp: %v", err)
	}

	if len(app.Backends) != 2 || len(app.Services) != 2 {
		t.Fatalf("Expected 2 backends and 2 services but got: len(backends)=%d, len(services)=%d", len(app.Backends), len(app.Services))
	}
	args := flag.Args()
	if len(args) != 3 || args[0] != "/testdata/test.sh" {
		t.Errorf("Expected 3 args but got unexpected results: %v", args)
	}

	expectedTags := []string{"tag1", "tag2"}
	if !reflect.DeepEqual(app.Services[0].Tags, expectedTags) {
		t.Errorf("Expected tags %s for serviceA, but got: %s", expectedTags, app.Services[0].Tags)
	}

	if app.Services[1].Tags != nil {
		t.Errorf("Expected no tags for serviceB, but got: %s", app.Services[1].Tags)
	}

	if app.Services[0].TTL != 19 {
		t.Errorf("Expected ttl=19 for serviceA, but got: %d", app.Services[1].TTL)
	}

	if app.Services[1].TTL != 103 {
		t.Errorf("Expected ttl=103 for serviceB, but got: %d", app.Services[1].TTL)
	}

	if app.Backends[0].Tag != "dev" {
		t.Errorf("Expected tag %s for upstreamA, but got: %s", "dev", app.Backends[0].Tag)
	}

	if app.Backends[1].Tag != "" {
		t.Errorf("Expected no tag for upstreamB, but got: %s", app.Backends[1].Tag)
	}

	validateCommandParsed(t, "preStart", app.PreStartCmd,
		"/bin/to/preStart.sh", []string{"arg1", "arg2"})
	validateCommandParsed(t, "preStop", app.PreStopCmd,
		"/bin/to/preStop.sh", []string{"arg1", "arg2"})
	validateCommandParsed(t, "postStop", app.PostStopCmd,
		"/bin/to/postStop.sh", nil) //[]string{})
}

func TestServiceConfigRequiredFields(t *testing.T) {
	// Missing `name`
	var testJSON = `{"consul": "consul:8500", "services": [
                           {"name": "", "port": 8080, "poll": 30, "ttl": 19 }]}`
	validateParseError(t, testJSON, []string{"`name`"})

	// Missing `poll`
	testJSON = `{"consul": "consul:8500", "services": [
                       {"name": "name", "port": 8080, "ttl": 19}]}`
	validateParseError(t, testJSON, []string{"`poll`"})

	// Missing `ttl`
	testJSON = `{"consul": "consul:8500", "services": [
                       {"name": "name", "port": 8080, "poll": 19}]}`
	validateParseError(t, testJSON, []string{"`ttl`"})

	testJSON = `{"consul": "consul:8500", "services": [
                       {"name": "name", "poll": 19, "ttl": 19}]}`
	validateParseError(t, testJSON, []string{"`port`"})
}

func TestBackendConfigRequiredFields(t *testing.T) {
	// Missing `name`
	var testJSON = `{"consul": "consul:8500", "backends": [
                           {"name": "", "poll": 30, "onChange": "true"}]}`
	validateParseError(t, testJSON, []string{"`name`"})

	// Missing `poll`
	testJSON = `{"consul": "consul:8500", "backends": [
                       {"name": "name", "onChange": "true"}]}`
	validateParseError(t, testJSON, []string{"`poll`"})

	// Missing `onChange`
	testJSON = `{"consul": "consul:8500", "backends": [
                       {"name": "name", "poll": 19 }]}`
	validateParseError(t, testJSON, []string{"`onChange`"})
}

func TestInvalidConfigNoConfigFlag(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	os.Args = []string{"this", "/testdata/test.sh", "invalid1", "--debug"}
	if _, err := LoadApp(); err != nil && err.Error() != "-config flag is required" {
		t.Errorf("Expected error but got %s", err)
	}
}

func TestInvalidConfigParseNoDiscovery(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testParseExpectError(t, "{}", "No discovery backend defined")
}

func TestInvalidConfigParseFile(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testParseExpectError(t, "file:///xxxx",
		"Could not read config file: open /xxxx: no such file or directory")
}

func TestInvalidConfigParseNotJson(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testParseExpectError(t, "<>",
		"Parse error at line:col [1:1]")
}

func TestJSONTemplateParseError(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testParseExpectError(t,
		`{
    "test": {{ .NO_SUCH_KEY }},
    "test2": "hello"
}`,
		"Parse error at line:col [2:13]")
}

func TestJSONTemplateParseError2(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testParseExpectError(t,
		`{
    "test1": "1",
    "test2": 2,
    "test3": false,
    test2: "hello"
}`,
		"Parse error at line:col [5:5]")
}

func TestParseTrailingComma(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testParseExpectError(t,
		`{
			"consul": "consul:8500",
			"tasks": [{
				"command": ["echo","hi"]
			},
		]
	}`, "Do you have an extra comma somewhere?")
}

func TestRenderArgs(t *testing.T) {
	flags := []string{"-name", "{{ .HOSTNAME }}"}
	expected, _ := os.Hostname()
	if got := getArgs(flags)[1]; got != expected {
		t.Errorf("Expected %v but got %v for rendered hostname", expected, got)
	}

	// invalid template should just be returned unchanged
	flags = []string{"-name", "{{ .HOSTNAME }"}
	expected = "{{ .HOSTNAME }"
	if got := getArgs(flags)[1]; got != expected {
		t.Errorf("Expected %v but got %v for unrendered hostname", expected, got)
	}
}

func TestMetricServiceCreation(t *testing.T) {

	jsonFragment := `{
    "consul": "consul:8500",
    "telemetry": {
      "port": 9090
    }
  }`
	if app, err := NewApp(jsonFragment); err != nil {
		t.Fatalf("Got error while initializing config: %v", err)
	} else {
		if len(app.Services) != 1 {
			t.Errorf("Expected telemetry service but got %v", app.Services)
		} else {
			service := app.Services[0]
			if service.Name != "containerpilot" {
				t.Errorf("Got incorrect service back: %v", service)
			}
			for _, envVar := range os.Environ() {
				if strings.HasPrefix(envVar, "CONTAINERPILOT_CONTAINERPILOT_IP") {
					return
				}
			}
			t.Errorf("Did not find CONTAINERPILOT_CONTAINERPILOT_IP env var")
		}
	}
}

func TestPidEnvVar(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	os.Args = []string{"this", "-config", "{}", "/testdata/test.sh"}
	if _, err := LoadApp(); err == nil {
		t.Fatalf("Expected error in LoadApp but got none")
	}
	if pid := os.Getenv("CONTAINERPILOT_PID"); pid == "" {
		t.Errorf("Expected CONTAINERPILOT_PID to be set even on error")
	}
}

// ----------------------------------------------------
// test helpers

func argTestSetup() []string {
	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
	flag.Usage = nil
	return os.Args
}

func argTestCleanup(oldArgs []string) {
	os.Args = oldArgs
}

func testParseExpectError(t *testing.T, testJSON string, expected string) {
	os.Args = []string{"this", "-config", testJSON, "/testdata/test.sh", "test", "--debug"}
	if _, err := LoadApp(); err != nil && !strings.Contains(err.Error(), expected) {
		t.Errorf("Expected %s but got %s", expected, err)
	}
}

func validateParseError(t *testing.T, testJSON string, matchStrings []string) {
	if _, err := NewApp(testJSON); err == nil {
		t.Errorf("Expected error parsing config")
	} else {
		for _, match := range matchStrings {
			if !strings.Contains(err.Error(), match) {
				t.Errorf("Expected message does not contain %s: %s", match, err)
			}
		}
	}
}

func validateCommandParsed(t *testing.T, name string, parsed *commands.Command,
	expectedExec string, expectedArgs []string) {
	if parsed == nil {
		t.Errorf("%s not configured", name)
	}
	if !reflect.DeepEqual(parsed.Exec, expectedExec) {
		t.Errorf("%s executable not configured: %s != %s", name, parsed.Exec, expectedExec)
	}
	if !reflect.DeepEqual(parsed.Args, expectedArgs) {
		t.Errorf("%s arguments not configured: %s != %s", name, parsed.Args, expectedArgs)
	}
}
<commit_msg>Add unit tests for new -render function.<commit_after>package core

import (
	"flag"
	"os"
	"os/exec"
	"reflect"
	"strings"
	"testing"

	"github.com/joyent/containerpilot/commands"
)

// ------------------------------------------

var testJSON = `{
	"consul": "consul:8500",
	"preStart": "/bin/to/preStart.sh arg1 arg2",
	"preStop": ["/bin/to/preStop.sh","arg1","arg2"],
	"postStop": ["/bin/to/postStop.sh"],
	"stopTimeout": 5,
	"services": [
			{
					"name": "serviceA",
					"port": 8080,
					"interfaces": "eth0",
					"health": "/bin/to/healthcheck/for/service/A.sh",
					"poll": 30,
					"ttl": "19",
					"tags": ["tag1","tag2"]
			},
			{
					"name": "serviceB",
					"port": 5000,
					"interfaces": ["ethwe","eth0"],
					"health": "/bin/to/healthcheck/for/service/B.sh",
					"poll": 30,
					"ttl": 103
			}
	],
	"backends": [
			{
					"name": "upstreamA",
					"poll": 11,
					"onChange": "/bin/to/onChangeEvent/for/upstream/A.sh {{.TEST}}",
					"tag": "dev"
			},
			{
					"name": "upstreamB",
					"poll": 79,
					"onChange": "/bin/to/onChangeEvent/for/upstream/B.sh {{.ENV_NOT_FOUND}}"
			},
            {
                    "name": "upstreamC{{.TEST}}"
                    "poll": 11,
                    "onChange": "/bin/to/onChangeEvent/for/upstream.C.sh",
                    "tag": "dev"
            }
	]
}
`

func TestValidConfigParse(t *testing.T) {
	defer argTestCleanup(argTestSetup())

	os.Setenv("TEST", "HELLO")
	os.Args = []string{"this", "-config", testJSON, "/testdata/test.sh", "valid1", "--debug"}
	app, err := LoadApp()
	if err != nil {
		t.Fatalf("Unexpected error in LoadApp: %v", err)
	}

	if len(app.Backends) != 2 || len(app.Services) != 2 {
		t.Fatalf("Expected 2 backends and 2 services but got: len(backends)=%d, len(services)=%d", len(app.Backends), len(app.Services))
	}
	args := flag.Args()
	if len(args) != 3 || args[0] != "/testdata/test.sh" {
		t.Errorf("Expected 3 args but got unexpected results: %v", args)
	}

	expectedTags := []string{"tag1", "tag2"}
	if !reflect.DeepEqual(app.Services[0].Tags, expectedTags) {
		t.Errorf("Expected tags %s for serviceA, but got: %s", expectedTags, app.Services[0].Tags)
	}

	if app.Services[1].Tags != nil {
		t.Errorf("Expected no tags for serviceB, but got: %s", app.Services[1].Tags)
	}

	if app.Services[0].TTL != 19 {
		t.Errorf("Expected ttl=19 for serviceA, but got: %d", app.Services[1].TTL)
	}

	if app.Services[1].TTL != 103 {
		t.Errorf("Expected ttl=103 for serviceB, but got: %d", app.Services[1].TTL)
	}

	if app.Backends[0].Tag != "dev" {
		t.Errorf("Expected tag %s for upstreamA, but got: %s", "dev", app.Backends[0].Tag)
	}

	if app.Backends[1].Tag != "" {
		t.Errorf("Expected no tag for upstreamB, but got: %s", app.Backends[1].Tag)
	}

	validateCommandParsed(t, "preStart", app.PreStartCmd,
		"/bin/to/preStart.sh", []string{"arg1", "arg2"})
	validateCommandParsed(t, "preStop", app.PreStopCmd,
		"/bin/to/preStop.sh", []string{"arg1", "arg2"})
	validateCommandParsed(t, "postStop", app.PostStopCmd,
		"/bin/to/postStop.sh", nil) //[]string{})
}

func TestServiceConfigRequiredFields(t *testing.T) {
	// Missing `name`
	var testJSON = `{"consul": "consul:8500", "services": [
                           {"name": "", "port": 8080, "poll": 30, "ttl": 19 }]}`
	validateParseError(t, testJSON, []string{"`name`"})

	// Missing `poll`
	testJSON = `{"consul": "consul:8500", "services": [
                       {"name": "name", "port": 8080, "ttl": 19}]}`
	validateParseError(t, testJSON, []string{"`poll`"})

	// Missing `ttl`
	testJSON = `{"consul": "consul:8500", "services": [
                       {"name": "name", "port": 8080, "poll": 19}]}`
	validateParseError(t, testJSON, []string{"`ttl`"})

	testJSON = `{"consul": "consul:8500", "services": [
                       {"name": "name", "poll": 19, "ttl": 19}]}`
	validateParseError(t, testJSON, []string{"`port`"})
}

func TestBackendConfigRequiredFields(t *testing.T) {
	// Missing `name`
	var testJSON = `{"consul": "consul:8500", "backends": [
                           {"name": "", "poll": 30, "onChange": "true"}]}`
	validateParseError(t, testJSON, []string{"`name`"})

	// Missing `poll`
	testJSON = `{"consul": "consul:8500", "backends": [
                       {"name": "name", "onChange": "true"}]}`
	validateParseError(t, testJSON, []string{"`poll`"})

	// Missing `onChange`
	testJSON = `{"consul": "consul:8500", "backends": [
                       {"name": "name", "poll": 19 }]}`
	validateParseError(t, testJSON, []string{"`onChange`"})
}

func TestInvalidConfigNoConfigFlag(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	os.Args = []string{"this", "/testdata/test.sh", "invalid1", "--debug"}
	if _, err := LoadApp(); err != nil && err.Error() != "-config flag is required" {
		t.Errorf("Expected error but got %s", err)
	}
}

func TestInvalidConfigParseNoDiscovery(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testParseExpectError(t, "{}", "No discovery backend defined")
}

func TestInvalidConfigParseFile(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testParseExpectError(t, "file:///xxxx",
		"Could not read config file: open /xxxx: no such file or directory")
}

func TestInvalidConfigParseNotJson(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testParseExpectError(t, "<>",
		"Parse error at line:col [1:1]")
}

func TestJSONTemplateParseError(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testParseExpectError(t,
		`{
    "test": {{ .NO_SUCH_KEY }},
    "test2": "hello"
}`,
		"Parse error at line:col [2:13]")
}

func TestJSONTemplateParseError2(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testParseExpectError(t,
		`{
    "test1": "1",
    "test2": 2,
    "test3": false,
    test2: "hello"
}`,
		"Parse error at line:col [5:5]")
}

func TestParseTrailingComma(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testParseExpectError(t,
		`{
			"consul": "consul:8500",
			"tasks": [{
				"command": ["echo","hi"]
			},
		]
	}`, "Do you have an extra comma somewhere?")
}

func TestRenderArgs(t *testing.T) {
	flags := []string{"-name", "{{ .HOSTNAME }}"}
	expected, _ := os.Hostname()
	if got := getArgs(flags)[1]; got != expected {
		t.Errorf("Expected %v but got %v for rendered hostname", expected, got)
	}

	// invalid template should just be returned unchanged
	flags = []string{"-name", "{{ .HOSTNAME }"}
	expected = "{{ .HOSTNAME }"
	if got := getArgs(flags)[1]; got != expected {
		t.Errorf("Expected %v but got %v for unrendered hostname", expected, got)
	}
}

func TestMetricServiceCreation(t *testing.T) {

	jsonFragment := `{
    "consul": "consul:8500",
    "telemetry": {
      "port": 9090
    }
  }`
	if app, err := NewApp(jsonFragment); err != nil {
		t.Fatalf("Got error while initializing config: %v", err)
	} else {
		if len(app.Services) != 1 {
			t.Errorf("Expected telemetry service but got %v", app.Services)
		} else {
			service := app.Services[0]
			if service.Name != "containerpilot" {
				t.Errorf("Got incorrect service back: %v", service)
			}
			for _, envVar := range os.Environ() {
				if strings.HasPrefix(envVar, "CONTAINERPILOT_CONTAINERPILOT_IP") {
					return
				}
			}
			t.Errorf("Did not find CONTAINERPILOT_CONTAINERPILOT_IP env var")
		}
	}
}

func TestPidEnvVar(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	os.Args = []string{"this", "-config", "{}", "/testdata/test.sh"}
	if _, err := LoadApp(); err == nil {
		t.Fatalf("Expected error in LoadApp but got none")
	}
	if pid := os.Getenv("CONTAINERPILOT_PID"); pid == "" {
		t.Errorf("Expected CONTAINERPILOT_PID to be set even on error")
	}
}

func TestInvalidRenderConfigFile(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testRenderExpectError(t, "file:///xxxx", "-",
		"Could not read config file: open /xxxx: no such file or directory")
}

func TestInvalidRenderFileConfig(t *testing.T) {
	defer argTestCleanup(argTestSetup())
	testRenderExpectError(t, testJSON, "file:///a/b/c/d/e/f.json",
		"Could not write config file: open /a/b/c/d/e/f.json: no such file or directory")
}

func TestRenderConfig(t *testing.T) {
	// Because of the "exit(0)" in LoadApp, we need to use this testing pattern
	// http://stackoverflow.com/questions/26225513/how-to-test-os-exit-scenarios-in-go
	if render_file := os.Getenv("__RENDER_FILE"); render_file != "" {
		defer argTestCleanup(argTestSetup())
		os.Args = []string{"this", "-config", testJSON, "-render", render_file}
		_, err := LoadApp()
		t.Fatalf("LoadApp failed with err %v", err)
		return
	}
}

func TestRenderConfigFileStdout(t *testing.T) {
	// Because of the "exit(0)" in LoadApp, we need to use this testing pattern
	// http://stackoverflow.com/questions/26225513/how-to-test-os-exit-scenarios-in-go
	defer os.Remove("testJSON.json")
	defer os.Remove("testJSON-stdout.json")

	// Render to file
	cmd := exec.Command(os.Args[0], "-test.run=TestRenderConfig")
	cmd.Env = append(os.Environ(), "__RENDER_FILE=file://testJSON.json")
	if err := cmd.Run(); err != nil {
		t.Fatalf("process ran with err %v, want exit status 0", err)
	}
	if exists, err := fileExists("testJSON.json"); !exists || err != nil {
		t.Errorf("Expected file testJSON.json to exist.")
	}

	// Render to stdout
	cmd = exec.Command(os.Args[0], "-test.run=TestRenderConfig")
	cmd.Env = append(os.Environ(), "__RENDER_FILE=-")
	cmd.Stdout, _ = os.Create("testJSON-stdout.json")
	if err := cmd.Run(); err != nil {
		t.Fatalf("process ran with err %v, want exit status 0", err)
	}

	// Assert they are the same size, should suffice to accept that
	// the files are the same.
	a_file, _ := os.Open("testJSON.json")
	a_file_stat, _ := a_file.Stat()
	b_file, _ := os.Open("testJSON-stdout.json")
	b_file_stat, _ := b_file.Stat()
	if a_file_stat.Size() != b_file_stat.Size() {
		t.Fatalf("Expected the rendered files to be of the same size")
	}
	return
}

func TestRenderConfigIsValid(t *testing.T) {
	defer os.Remove("testJSON.json")

	// Render to file
	cmd := exec.Command(os.Args[0], "-test.run=TestRenderConfig")
	cmd.Env = append(os.Environ(), "TEST=SUCCESS", "__RENDER_FILE=file://testJSON.json")
	cmd.Run()

	defer argTestCleanup(argTestSetup())

	os.Setenv("TEST", "FAILED")
	os.Args = []string{"this", "-config", "file://testJSON.json", "/testdata/test.sh", "valid1", "--debug"}
	app, err := LoadApp()
	if err != nil {
		t.Fatalf("Unexpected error in LoadApp: %v", err)
	}
	if strings.HasSuffix(app.Backends[2].Name, "SUCCESS") {
		t.Errorf("Expected Backend[2] name to end in %s, but got: %s", "SUCCESS", app.Backends[2].Name)
	}

}

// ----------------------------------------------------
// test helpers

func argTestSetup() []string {
	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
	flag.Usage = nil
	return os.Args
}

func argTestCleanup(oldArgs []string) {
	os.Args = oldArgs
}

func testRenderExpectError(t *testing.T, testJSON string, render string, expected string) {
	os.Args = []string{"this", "-config", testJSON, "-render", render}
	if _, err := LoadApp(); err != nil && !strings.Contains(err.Error(), expected) {
		t.Errorf("Excepted %s but got %s", expected, err)
	}
}

func testParseExpectError(t *testing.T, testJSON string, expected string) {
	os.Args = []string{"this", "-config", testJSON, "/testdata/test.sh", "test", "--debug"}
	if _, err := LoadApp(); err != nil && !strings.Contains(err.Error(), expected) {
		t.Errorf("Expected %s but got %s", expected, err)
	}
}

func validateParseError(t *testing.T, testJSON string, matchStrings []string) {
	if _, err := NewApp(testJSON); err == nil {
		t.Errorf("Expected error parsing config")
	} else {
		for _, match := range matchStrings {
			if !strings.Contains(err.Error(), match) {
				t.Errorf("Expected message does not contain %s: %s", match, err)
			}
		}
	}
}

func validateCommandParsed(t *testing.T, name string, parsed *commands.Command,
	expectedExec string, expectedArgs []string) {
	if parsed == nil {
		t.Errorf("%s not configured", name)
	}
	if !reflect.DeepEqual(parsed.Exec, expectedExec) {
		t.Errorf("%s executable not configured: %s != %s", name, parsed.Exec, expectedExec)
	}
	if !reflect.DeepEqual(parsed.Args, expectedArgs) {
		t.Errorf("%s arguments not configured: %s != %s", name, parsed.Args, expectedArgs)
	}
}

func fileExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}
<|endoftext|>