<commit_before>package server

import (
	"net"
	"sync"
	"time"

	"github.com/cashshuffle/cashshuffle/message"
	"github.com/nats-io/nuid"
)

const (
	// banTime is the amount of time to ban an IP.
	banTime = 30 * time.Minute

	// banScoreTick is the ban score increment on each pool ban.
	banScoreTick = 1

	// maxBanScore is the score the connection much reach to
	// be banned by IP.
	maxBanScore = 3
)

// Tracker is used to track connections to the server.
type Tracker struct {
	bannedIPs               map[string]*banData
	connections             map[net.Conn]*trackerData
	verificationKeys        map[string]net.Conn
	mutex                   sync.RWMutex
	pools                   map[int]map[uint32]*trackerData
	poolAmounts             map[int]uint64
	poolVersions            map[int]uint64
	poolTypes               map[int]message.ShuffleType
	poolSize                int
	fullPools               map[int]interface{}
	shufflePort             int
	shuffleWebSocketPort    int
	torShufflePort          int
	torShuffleWebSocketPort int
}

// banData is the data required to track IP bans.
type banData struct {
	score uint32
}

// trackerData is data needed about each connection.
type trackerData struct {
	mutex           sync.Mutex
	sessionID       []byte
	number          uint32
	conn            net.Conn
	verificationKey string
	pool            int
	poolSize        int
	bannedBy        map[string]interface{}
	amount          uint64
	version         uint64
	shuffleType     message.ShuffleType
}

// NewTracker instantiates a new tracker
func NewTracker(poolSize int, shufflePort int, shuffleWebSocketPort int, torShufflePort int, torShuffleWebSocketPort int) *Tracker {
	return &Tracker{
		poolSize:                poolSize,
		bannedIPs:               make(map[string]*banData),
		connections:             make(map[net.Conn]*trackerData),
		verificationKeys:        make(map[string]net.Conn),
		pools:                   make(map[int]map[uint32]*trackerData),
		poolAmounts:             make(map[int]uint64),
		poolVersions:            make(map[int]uint64),
		poolTypes:               make(map[int]message.ShuffleType),
		fullPools:               make(map[int]interface{}),
		shufflePort:             shufflePort,
		shuffleWebSocketPort:    shuffleWebSocketPort,
		torShufflePort:          torShufflePort,
		torShuffleWebSocketPort: torShuffleWebSocketPort,
	}
}

// add adds a connection to the tracker.
func (t *Tracker) add(data *trackerData) {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	t.verificationKeys[data.verificationKey] = data.conn

	data.sessionID = t.generateSessionID()

	t.connections[data.conn] = data

	data.pool, data.number = t.assignPool(data)
}

// remove removes the connection.
func (t *Tracker) remove(conn net.Conn) {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	td := t.connections[conn]
	if td != nil {
		if td.verificationKey != "" {
			delete(t.verificationKeys, td.verificationKey)
		}

		t.unassignPool(td)

		delete(t.connections, conn)
	}
}

// banned returns true if the player has been banned.
func (t *Tracker) banned(data *trackerData) bool {
	return data.poolSize <= (len(data.bannedBy) + 1)
}

// count returns the number of connections to the server.
func (t *Tracker) count() int {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	return len(t.connections)
}

// bannedIP returns true if the player has been banned from the server.
func (t *Tracker) bannedIP(conn net.Conn) bool {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())

	banData := t.bannedIPs[ip]
	if banData != nil && banData.score >= maxBanScore {
		return true
	}

	return false
}

// banIP bans an IP on the server.
func (t *Tracker) banIP(conn net.Conn) {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())

	if _, ok := t.bannedIPs[ip]; ok {
		t.bannedIPs[ip].score += banScoreTick
	} else {
		t.bannedIPs[ip] = &banData{
			score: banScoreTick,
		}
	}

	go t.cleanupBan(ip)
}

// cleanupBan is the decrementer on the ban score and
// cleans up IPs that no longer need to be tracked.
func (t *Tracker) cleanupBan(ip string) {
	time.Sleep(banTime)

	t.mutex.Lock()
	defer t.mutex.Unlock()

	if _, ok := t.bannedIPs[ip]; ok {
		t.bannedIPs[ip].score -= banScoreTick
	}

	if t.bannedIPs[ip].score == 0 {
		delete(t.bannedIPs, ip)
	}
}

// getVerifcationKeyConn gets the connection for a verification key.
func (t *Tracker) getVerificationKeyData(key string) *trackerData {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	if _, ok := t.verificationKeys[key]; ok {
		return t.connections[t.verificationKeys[key]]
	}

	return nil
}

// getTrackerdData returns trackerdata associated with a connection.
func (t *Tracker) getTrackerData(c net.Conn) *trackerData {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	return t.connections[c]
}

// getPoolSize returns the pool size for the connection.
func (t *Tracker) getPoolSize(pool int) int {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	return len(t.pools[pool])
}

// generateSessionID generates a unique session id.
// This method assumes the caller is holding the mutex.
func (t *Tracker) generateSessionID() []byte {
	n := nuid.New()

	return []byte(n.Next())
}

// assignPool assigns a user to a pool.
// This method assumes the caller is holding the mutex.
func (t *Tracker) assignPool(data *trackerData) (int, uint32) {
	num := 1

	for {
		if _, ok := t.pools[num]; ok {
			if t.poolAmounts[num] != data.amount {
				num = num + 1
				continue
			}

			if t.poolVersions[num] != data.version {
				num = num + 1
				continue
			}

			if t.poolTypes[num] != data.shuffleType {
				num = num + 1
				continue
			}

			if _, ok := t.fullPools[num]; ok {
				num = num + 1
				continue
			}
		}

		break
	}

	playerNum := uint32(1)
	if _, ok := t.pools[num]; !ok {
		t.pools[num] = make(map[uint32]*trackerData)
		t.pools[num][1] = data
		t.poolAmounts[num] = data.amount
		t.poolVersions[num] = data.version
		t.poolTypes[num] = data.shuffleType
	} else {
		for {
			if _, ok := t.pools[num][playerNum]; ok {
				playerNum = playerNum + 1
				continue
			}

			break
		}

		t.pools[num][playerNum] = data
	}

	if len(t.pools[num]) == t.poolSize {
		t.fullPools[num] = nil
	}

	return num, playerNum
}

// decreasePoolSize decreases the pool size being
// tracked in trackerData after a blame occurs.
func (t *Tracker) decreasePoolSize(pool int) {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	for _, td := range t.pools[pool] {
		if td == nil {
			continue
		}

		td.mutex.Lock()
		defer td.mutex.Unlock()

		td.poolSize--
	}
}

// unassignPool removes a user from a pool.
// This method assumes the caller is holding the mutex.
func (t *Tracker) unassignPool(td *trackerData) {
	delete(t.pools[td.pool], td.number)

	if len(t.pools[td.pool]) == 0 {
		delete(t.pools, td.pool)
		delete(t.fullPools, td.pool)
		delete(t.poolAmounts, td.pool)
		delete(t.poolVersions, td.pool)
		delete(t.poolTypes, td.pool)
	}
}
<commit_msg>Add lock on banned()<commit_after>package server

import (
	"net"
	"sync"
	"time"

	"github.com/cashshuffle/cashshuffle/message"
	"github.com/nats-io/nuid"
)

const (
	// banTime is the amount of time to ban an IP.
	banTime = 30 * time.Minute

	// banScoreTick is the ban score increment on each pool ban.
	banScoreTick = 1

	// maxBanScore is the score the connection much reach to
	// be banned by IP.
	maxBanScore = 3
)

// Tracker is used to track connections to the server.
type Tracker struct {
	bannedIPs               map[string]*banData
	connections             map[net.Conn]*trackerData
	verificationKeys        map[string]net.Conn
	mutex                   sync.RWMutex
	pools                   map[int]map[uint32]*trackerData
	poolAmounts             map[int]uint64
	poolVersions            map[int]uint64
	poolTypes               map[int]message.ShuffleType
	poolSize                int
	fullPools               map[int]interface{}
	shufflePort             int
	shuffleWebSocketPort    int
	torShufflePort          int
	torShuffleWebSocketPort int
}

// banData is the data required to track IP bans.
type banData struct {
	score uint32
}

// trackerData is data needed about each connection.
type trackerData struct {
	mutex           sync.Mutex
	sessionID       []byte
	number          uint32
	conn            net.Conn
	verificationKey string
	pool            int
	poolSize        int
	bannedBy        map[string]interface{}
	amount          uint64
	version         uint64
	shuffleType     message.ShuffleType
}

// NewTracker instantiates a new tracker
func NewTracker(poolSize int, shufflePort int, shuffleWebSocketPort int, torShufflePort int, torShuffleWebSocketPort int) *Tracker {
	return &Tracker{
		poolSize:                poolSize,
		bannedIPs:               make(map[string]*banData),
		connections:             make(map[net.Conn]*trackerData),
		verificationKeys:        make(map[string]net.Conn),
		pools:                   make(map[int]map[uint32]*trackerData),
		poolAmounts:             make(map[int]uint64),
		poolVersions:            make(map[int]uint64),
		poolTypes:               make(map[int]message.ShuffleType),
		fullPools:               make(map[int]interface{}),
		shufflePort:             shufflePort,
		shuffleWebSocketPort:    shuffleWebSocketPort,
		torShufflePort:          torShufflePort,
		torShuffleWebSocketPort: torShuffleWebSocketPort,
	}
}

// add adds a connection to the tracker.
func (t *Tracker) add(data *trackerData) {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	t.verificationKeys[data.verificationKey] = data.conn

	data.sessionID = t.generateSessionID()

	t.connections[data.conn] = data

	data.pool, data.number = t.assignPool(data)
}

// remove removes the connection.
func (t *Tracker) remove(conn net.Conn) {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	td := t.connections[conn]
	if td != nil {
		if td.verificationKey != "" {
			delete(t.verificationKeys, td.verificationKey)
		}

		t.unassignPool(td)

		delete(t.connections, conn)
	}
}

// banned returns true if the player has been banned.
func (t *Tracker) banned(data *trackerData) bool {
	data.mutex.Lock()
	defer data.mutex.Unlock()

	return data.poolSize <= (len(data.bannedBy) + 1)
}

// count returns the number of connections to the server.
func (t *Tracker) count() int {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	return len(t.connections)
}

// bannedIP returns true if the player has been banned from the server.
func (t *Tracker) bannedIP(conn net.Conn) bool {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())

	banData := t.bannedIPs[ip]
	if banData != nil && banData.score >= maxBanScore {
		return true
	}

	return false
}

// banIP bans an IP on the server.
func (t *Tracker) banIP(conn net.Conn) {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())

	if _, ok := t.bannedIPs[ip]; ok {
		t.bannedIPs[ip].score += banScoreTick
	} else {
		t.bannedIPs[ip] = &banData{
			score: banScoreTick,
		}
	}

	go t.cleanupBan(ip)
}

// cleanupBan is the decrementer on the ban score and
// cleans up IPs that no longer need to be tracked.
func (t *Tracker) cleanupBan(ip string) {
	time.Sleep(banTime)

	t.mutex.Lock()
	defer t.mutex.Unlock()

	if _, ok := t.bannedIPs[ip]; ok {
		t.bannedIPs[ip].score -= banScoreTick
	}

	if t.bannedIPs[ip].score == 0 {
		delete(t.bannedIPs, ip)
	}
}

// getVerifcationKeyConn gets the connection for a verification key.
func (t *Tracker) getVerificationKeyData(key string) *trackerData {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	if _, ok := t.verificationKeys[key]; ok {
		return t.connections[t.verificationKeys[key]]
	}

	return nil
}

// getTrackerdData returns trackerdata associated with a connection.
func (t *Tracker) getTrackerData(c net.Conn) *trackerData {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	return t.connections[c]
}

// getPoolSize returns the pool size for the connection.
func (t *Tracker) getPoolSize(pool int) int {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	return len(t.pools[pool])
}

// generateSessionID generates a unique session id.
// This method assumes the caller is holding the mutex.
func (t *Tracker) generateSessionID() []byte {
	n := nuid.New()

	return []byte(n.Next())
}

// assignPool assigns a user to a pool.
// This method assumes the caller is holding the mutex.
func (t *Tracker) assignPool(data *trackerData) (int, uint32) {
	num := 1

	for {
		if _, ok := t.pools[num]; ok {
			if t.poolAmounts[num] != data.amount {
				num = num + 1
				continue
			}

			if t.poolVersions[num] != data.version {
				num = num + 1
				continue
			}

			if t.poolTypes[num] != data.shuffleType {
				num = num + 1
				continue
			}

			if _, ok := t.fullPools[num]; ok {
				num = num + 1
				continue
			}
		}

		break
	}

	playerNum := uint32(1)
	if _, ok := t.pools[num]; !ok {
		t.pools[num] = make(map[uint32]*trackerData)
		t.pools[num][1] = data
		t.poolAmounts[num] = data.amount
		t.poolVersions[num] = data.version
		t.poolTypes[num] = data.shuffleType
	} else {
		for {
			if _, ok := t.pools[num][playerNum]; ok {
				playerNum = playerNum + 1
				continue
			}

			break
		}

		t.pools[num][playerNum] = data
	}

	if len(t.pools[num]) == t.poolSize {
		t.fullPools[num] = nil
	}

	return num, playerNum
}

// decreasePoolSize decreases the pool size being
// tracked in trackerData after a blame occurs.
func (t *Tracker) decreasePoolSize(pool int) {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	for _, td := range t.pools[pool] {
		if td == nil {
			continue
		}

		td.mutex.Lock()
		defer td.mutex.Unlock()

		td.poolSize--
	}
}

// unassignPool removes a user from a pool.
// This method assumes the caller is holding the mutex.
func (t *Tracker) unassignPool(td *trackerData) {
	delete(t.pools[td.pool], td.number)

	if len(t.pools[td.pool]) == 0 {
		delete(t.pools, td.pool)
		delete(t.fullPools, td.pool)
		delete(t.poolAmounts, td.pool)
		delete(t.poolVersions, td.pool)
		delete(t.poolTypes, td.pool)
	}
}
<|endoftext|>


<commit_before>package pullcord

import (
	"errors"
	"github.com/fitstar/falcore"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"net/http"
	"strings"
	"testing"
)

func testable_ConditionalFilter(condition *bool, err *error) falcore.RequestFilter {
	return NewConditionalFilter(
		func(req *falcore.Request) (bool, error) {
			return *condition, *err
		},
		falcore.NewRequestFilter(
			func(req *falcore.Request) *http.Response {
				return falcore.StringResponse(
					req.HttpRequest,
					200,
					nil,
					"
condition was true
",
				)
			},
		),
		falcore.NewRequestFilter(
			func(req *falcore.Request) *http.Response {
				return falcore.StringResponse(
					req.HttpRequest,
					200,
					nil,
					"
condition was false
",
				)
			},
		),
		falcore.NewRequestFilter(
			func(req *falcore.Request) *http.Response {
				return falcore.StringResponse(
					req.HttpRequest,
					500,
					nil,
					"
an error occurred
",
				)
			},
		),
	)
}

func TestConditionalFilterTrue(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := true
	var test_err error
	test_err = nil
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 200, response.StatusCode)

	contents, err := ioutil.ReadAll(response.Body)
	assert.NoError(t, err)
	assert.True(t, strings.Contains(string(contents), "condition was true"))
	assert.False(t, strings.Contains(string(contents), "condition was false"))
	assert.False(t, strings.Contains(string(contents), "an error occurred"))
}

func TestConditionalFilterFalse(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := false
	var test_err error
	test_err = nil
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 200, response.StatusCode)

	contents, err := ioutil.ReadAll(response.Body)
	assert.NoError(t, err)
	assert.False(t, strings.Contains(string(contents), "condition was true"))
	assert.True(t, strings.Contains(string(contents), "condition was false"))
	assert.False(t, strings.Contains(string(contents), "an error occurred"))
}

func TestConditionalFilterError(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := true
	var test_err error
	test_err = errors.New("test error")
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 500, response.StatusCode)

	contents, err := ioutil.ReadAll(response.Body)
	assert.NoError(t, err)
	assert.False(t, strings.Contains(string(contents), "condition was true"))
	assert.False(t, strings.Contains(string(contents), "condition was false"))
	assert.True(t, strings.Contains(string(contents), "an error occurred"))
}

func TestConditionalFilterTrueFalse(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := true
	var test_err error
	test_err = nil
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response1 := falcore.TestWithRequest(request, conditional_filter, nil)
	condition = false
	_, response2 := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 200, response1.StatusCode)
	assert.Equal(t, 200, response2.StatusCode)

	contents1, err := ioutil.ReadAll(response1.Body)
	assert.NoError(t, err)
	contents2, err := ioutil.ReadAll(response2.Body)
	assert.NoError(t, err)
	assert.True(t, strings.Contains(string(contents1), "condition was true"))
	assert.False(t, strings.Contains(string(contents1), "condition was false"))
	assert.False(t, strings.Contains(string(contents1), "an error occurred"))
	assert.False(t, strings.Contains(string(contents2), "condition was true"))
	assert.True(t, strings.Contains(string(contents2), "condition was false"))
	assert.False(t, strings.Contains(string(contents2), "an error occurred"))
}

func TestConditionalFilterFalseTrue(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := false
	var test_err error
	test_err = nil
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response1 := falcore.TestWithRequest(request, conditional_filter, nil)
	condition = true
	_, response2 := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 200, response1.StatusCode)
	assert.Equal(t, 200, response2.StatusCode)

	contents1, err := ioutil.ReadAll(response1.Body)
	assert.NoError(t, err)
	contents2, err := ioutil.ReadAll(response2.Body)
	assert.NoError(t, err)
	assert.False(t, strings.Contains(string(contents1), "condition was true"))
	assert.True(t, strings.Contains(string(contents1), "condition was false"))
	assert.False(t, strings.Contains(string(contents1), "an error occurred"))
	assert.True(t, strings.Contains(string(contents2), "condition was true"))
	assert.False(t, strings.Contains(string(contents2), "condition was false"))
	assert.False(t, strings.Contains(string(contents2), "an error occurred"))
}

func TestConditionalFilterTrueError(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := true
	var test_err error
	test_err = nil
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response1 := falcore.TestWithRequest(request, conditional_filter, nil)
	test_err = errors.New("test error")
	condition = false
	_, response2 := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 200, response1.StatusCode)
	assert.Equal(t, 500, response2.StatusCode)

	contents1, err := ioutil.ReadAll(response1.Body)
	assert.NoError(t, err)
	contents2, err := ioutil.ReadAll(response2.Body)
	assert.NoError(t, err)
	assert.True(t, strings.Contains(string(contents1), "condition was true"))
	assert.False(t, strings.Contains(string(contents1), "condition was false"))
	assert.False(t, strings.Contains(string(contents1), "an error occurred"))
	assert.False(t, strings.Contains(string(contents2), "condition was true"))
	assert.False(t, strings.Contains(string(contents2), "condition was false"))
	assert.True(t, strings.Contains(string(contents2), "an error occurred"))
}

func TestConditionalFilterErrorFalse(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := true
	var test_err error
	test_err = errors.New("test error")
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response1 := falcore.TestWithRequest(request, conditional_filter, nil)
	condition = false
	test_err = nil
	_, response2 := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 500, response1.StatusCode)
	assert.Equal(t, 200, response2.StatusCode)

	contents1, err := ioutil.ReadAll(response1.Body)
	assert.NoError(t, err)
	contents2, err := ioutil.ReadAll(response2.Body)
	assert.NoError(t, err)
	assert.False(t, strings.Contains(string(contents1), "condition was true"))
	assert.False(t, strings.Contains(string(contents1), "condition was false"))
	assert.True(t, strings.Contains(string(contents1), "an error occurred"))
	assert.False(t, strings.Contains(string(contents2), "condition was true"))
	assert.True(t, strings.Contains(string(contents2), "condition was false"))
	assert.False(t, strings.Contains(string(contents2), "an error occurred"))
}
<commit_msg>At the advice of @michaelbeam, removing the underscore.<commit_after>package pullcord

import (
	"errors"
	"github.com/fitstar/falcore"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"net/http"
	"strings"
	"testing"
)

func testableConditionalFilter(condition *bool, err *error) falcore.RequestFilter {
	return NewConditionalFilter(
		func(req *falcore.Request) (bool, error) {
			return *condition, *err
		},
		falcore.NewRequestFilter(
			func(req *falcore.Request) *http.Response {
				return falcore.StringResponse(
					req.HttpRequest,
					200,
					nil,
					"
condition was true
",
				)
			},
		),
		falcore.NewRequestFilter(
			func(req *falcore.Request) *http.Response {
				return falcore.StringResponse(
					req.HttpRequest,
					200,
					nil,
					"
condition was false
",
				)
			},
		),
		falcore.NewRequestFilter(
			func(req *falcore.Request) *http.Response {
				return falcore.StringResponse(
					req.HttpRequest,
					500,
					nil,
					"
an error occurred
",
				)
			},
		),
	)
}

func TestConditionalFilterTrue(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := true
	var test_err error
	test_err = nil
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 200, response.StatusCode)

	contents, err := ioutil.ReadAll(response.Body)
	assert.NoError(t, err)
	assert.True(t, strings.Contains(string(contents), "condition was true"))
	assert.False(t, strings.Contains(string(contents), "condition was false"))
	assert.False(t, strings.Contains(string(contents), "an error occurred"))
}

func TestConditionalFilterFalse(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := false
	var test_err error
	test_err = nil
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 200, response.StatusCode)

	contents, err := ioutil.ReadAll(response.Body)
	assert.NoError(t, err)
	assert.False(t, strings.Contains(string(contents), "condition was true"))
	assert.True(t, strings.Contains(string(contents), "condition was false"))
	assert.False(t, strings.Contains(string(contents), "an error occurred"))
}

func TestConditionalFilterError(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := true
	var test_err error
	test_err = errors.New("test error")
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 500, response.StatusCode)

	contents, err := ioutil.ReadAll(response.Body)
	assert.NoError(t, err)
	assert.False(t, strings.Contains(string(contents), "condition was true"))
	assert.False(t, strings.Contains(string(contents), "condition was false"))
	assert.True(t, strings.Contains(string(contents), "an error occurred"))
}

func TestConditionalFilterTrueFalse(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := true
	var test_err error
	test_err = nil
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response1 := falcore.TestWithRequest(request, conditional_filter, nil)
	condition = false
	_, response2 := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 200, response1.StatusCode)
	assert.Equal(t, 200, response2.StatusCode)

	contents1, err := ioutil.ReadAll(response1.Body)
	assert.NoError(t, err)
	contents2, err := ioutil.ReadAll(response2.Body)
	assert.NoError(t, err)
	assert.True(t, strings.Contains(string(contents1), "condition was true"))
	assert.False(t, strings.Contains(string(contents1), "condition was false"))
	assert.False(t, strings.Contains(string(contents1), "an error occurred"))
	assert.False(t, strings.Contains(string(contents2), "condition was true"))
	assert.True(t, strings.Contains(string(contents2), "condition was false"))
	assert.False(t, strings.Contains(string(contents2), "an error occurred"))
}

func TestConditionalFilterFalseTrue(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := false
	var test_err error
	test_err = nil
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response1 := falcore.TestWithRequest(request, conditional_filter, nil)
	condition = true
	_, response2 := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 200, response1.StatusCode)
	assert.Equal(t, 200, response2.StatusCode)

	contents1, err := ioutil.ReadAll(response1.Body)
	assert.NoError(t, err)
	contents2, err := ioutil.ReadAll(response2.Body)
	assert.NoError(t, err)
	assert.False(t, strings.Contains(string(contents1), "condition was true"))
	assert.True(t, strings.Contains(string(contents1), "condition was false"))
	assert.False(t, strings.Contains(string(contents1), "an error occurred"))
	assert.True(t, strings.Contains(string(contents2), "condition was true"))
	assert.False(t, strings.Contains(string(contents2), "condition was false"))
	assert.False(t, strings.Contains(string(contents2), "an error occurred"))
}

func TestConditionalFilterTrueError(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := true
	var test_err error
	test_err = nil
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response1 := falcore.TestWithRequest(request, conditional_filter, nil)
	test_err = errors.New("test error")
	condition = false
	_, response2 := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 200, response1.StatusCode)
	assert.Equal(t, 500, response2.StatusCode)

	contents1, err := ioutil.ReadAll(response1.Body)
	assert.NoError(t, err)
	contents2, err := ioutil.ReadAll(response2.Body)
	assert.NoError(t, err)
	assert.True(t, strings.Contains(string(contents1), "condition was true"))
	assert.False(t, strings.Contains(string(contents1), "condition was false"))
	assert.False(t, strings.Contains(string(contents1), "an error occurred"))
	assert.False(t, strings.Contains(string(contents2), "condition was true"))
	assert.False(t, strings.Contains(string(contents2), "condition was false"))
	assert.True(t, strings.Contains(string(contents2), "an error occurred"))
}

func TestConditionalFilterErrorFalse(t *testing.T) {
	/* setup */
	request, err := http.NewRequest("GET", "/", nil)
	assert.NoError(t, err)
	condition := true
	var test_err error
	test_err = errors.New("test error")
	conditional_filter := testable_ConditionalFilter(&condition, &test_err)

	/* run */
	_, response1 := falcore.TestWithRequest(request, conditional_filter, nil)
	condition = false
	test_err = nil
	_, response2 := falcore.TestWithRequest(request, conditional_filter, nil)

	/* check */
	assert.Equal(t, 500, response1.StatusCode)
	assert.Equal(t, 200, response2.StatusCode)

	contents1, err := ioutil.ReadAll(response1.Body)
	assert.NoError(t, err)
	contents2, err := ioutil.ReadAll(response2.Body)
	assert.NoError(t, err)
	assert.False(t, strings.Contains(string(contents1), "condition was true"))
	assert.False(t, strings.Contains(string(contents1), "condition was false"))
	assert.True(t, strings.Contains(string(contents1), "an error occurred"))
	assert.False(t, strings.Contains(string(contents2), "condition was true"))
	assert.True(t, strings.Contains(string(contents2), "condition was false"))
	assert.False(t, strings.Contains(string(contents2), "an error occurred"))
}
<|endoftext|>


<commit_before>/*
 Copyright 2015 Bluek404
 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 nwui

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"time"

	"github.com/gorilla/websocket"
)

var (
	// 用于生成随机数
	r    = rand.New(rand.NewSource(time.Now().UnixNano()))
	cons = make(map[string]interface{})
	temp = `<!DOCTYPE html>

<meta charset="utf-8">
<title>%v</title>

%v

`
)

func printInfo(v ...interface{}) {
	log.Println(append([]interface{}{"[nwui][Info]"}, v...)...)
}

func printError(v ...interface{}) {
	log.Println(append([]interface{}{"[nwui][Error]"}, v...)...)
}

// 生成控件ID
func NewControlID() string { return "_" + strconv.FormatInt(r.Int63(), 36) }

func GetConByID(id string) interface{} { return cons[id] }

// 显示窗口
// 必须在全部控件设置完毕后才能调用
func Show(window interface{}) {
	// 查找可用端口
	var port string
	for i := 7072; i <= 65536; i++ {
		p := strconv.Itoa(i)
		ln, err := net.Listen("tcp", "localhost:"+p)
		if err != nil {
			continue
		} else {
			port = p
			ln.Close()
			break
		}
	}
	if port == "" {
		printError("no port can use")
		os.Exit(1)
	}

	var (
		html     string
		css      string
		js       string
		events   = make(map[string]func(v string))
		upgrader = websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
		}
		sender = make(chan EventMsg)
		x      = make(map[string]bool)
	)

	vv := reflect.ValueOf(window).MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(sender)})

	vvv := vv[0].Interface().(Controls)
	for id, con := range vvv {
		if _, ok := x[con.Static.Name]; !ok {
			css += con.Static.CSS
			js += con.Static.JavaScript
			x[con.Static.Name] = true
		}
		cons[id] = con.V
		html += con.HTML
		for event, f := range con.Events {
			events[id+event] = f
		}
	}

	nwui := vv[1].Interface().(NWUI)
	exit := vv[2].Interface().(chan bool)

	http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(rw, temp, nwui.Name, css, html, "localhost:"+port, js)
		r.Body.Close()
	})
	http.HandleFunc("/ws", func(rw http.ResponseWriter, r *http.Request) {
		conn, err := upgrader.Upgrade(rw, r, nil)
		if err != nil && err != io.EOF {
			printError(err)
			return
		}

		// 用于接收事件
		go func() {
			for {
				messageType, p, err := conn.ReadMessage()
				if err != nil {
					printError(err)
					return
				}

				if messageType == websocket.TextMessage {
					var msg EventMsg
					err = json.Unmarshal(p, &msg)
					if err != nil {
						printError(err)
						return
					}

					// 执行事件所绑定的函数
					f, ok := events[msg.ID+msg.Event]
					if ok {
						f(msg.Value)
					} else {
						printError("unfind event:", msg.ID+msg.Event)
					}
				}
			}
		}()

		for {
			// 发送消息给前端
			m := <-sender
			msg, err := json.Marshal(&m)
			if err != nil {
				printError(err)
				return
			}
			err = conn.WriteMessage(websocket.TextMessage, msg)
			if err != nil {
				printError(err)
			}
		}
	})

	go func() {
		printInfo("running on localhost:" + port)
		err := http.ListenAndServe("localhost:"+port, nil)
		if err != nil {
			panic(err)
		}
	}()

	<-exit
}

// TODO: 移植自定义窗口控件
// TODO: 分离nwui框架和控件
// TODO: 更多控件+主题
// TODO: 自动启动nwjs

// nwui窗口
type Window struct {
	Title     string
	Width     int
	Height    int
	MaxWidth  int
	MaxHeight int
	MinWidth  int
	MinHeight int
	Controls  []interface{}
	OnExit    func()
	exit      chan bool
}

func (w *Window) Init(sender chan EventMsg) (Controls, NWUI, chan bool) {
	w.exit = make(chan bool)

	var (
		id   = NewControlID()
		cons = Controls{
			id: Control{
				Events: map[string]func(v string){"exit": func(v string) {
					if w.OnExit != nil {
						w.OnExit()
					}
					w.exit <- true
					return
				}},
				Static: ConStatic{
					Name: "Window",
					JavaScript: `
window.onunload = function() {
	send("` + id + `", "exit", "");
}`,
				},
			},
		}
		x = make(map[string]bool)
	)

	// 初始化控件
	for _, v := range w.Controls {
		// 返回html string, javascript string, events map[string]func(v string)
		vv := reflect.ValueOf(v).MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(sender)})

		vvv := vv[0].Interface().(Controls)

		for id, con := range vvv {
			// 检查ID是否重复
			if _, ok := x[id]; ok {
				panic("duplicate id: " + id)
			}

			cons[id] = con
		}
	}

	return cons, NWUI{
		Name: w.Title,
	}, w.exit
}

/*
defaultTheme = `
.frame {
    position: absolute;
    left: 0px;
    top: 0px;
    width: 100%;
    height: 32px;
    background-color: #424242;
    -webkit-app-region: drag;
}
.frame .title {
    color: white;
    position: absolute;
    left: 12px;
    width: 80%;
    margin-top: 6px;
    margin-bottom: 6px;
    font-size: 11pt;
}
.frame button#close {
    position: absolute;
    left: auto;
    right: 12px;
    width: auto;
    font-size: 11pt;
    -webkit-app-region: no-drag;
}
.main {
    margin-top: 40px;
}`
// 创建一个新的自定义窗口边框
// 一个 Window 中只能有一个 Frame
func NewFrame(title string, con ...Control) Frame {
	return &frame{
		title:    title,
		controls: con,
		events:   make(map[string]func(v string)),
	}
}
// 窗口边框
type Frame interface {
	Control
}
type frame struct {
	title    string
	js       string
	controls []Control
	events   map[string]func(v string)
	send     func(f, v string)
}
func (f *frame) getEvents() map[string]func(v string) {
	return f.events
}
func (f *frame) setSendFunc(fc func(f, v string)) {
	f.send = fc
}
func (f *frame) genHTML() string {
	// 转换内部控件
	var html string
	for _, v := range f.controls {
		v.setSendFunc(f.send)
		html += v.genHTML()
		f.js += v.genJavaScript()
		for e, fc := range v.getEvents() {
			f.events[e] = fc
		}
	}
	return `

` + f.title + `
<button id="close">x</button>

` + html + `

`
}
func (f *frame) genJavaScript() string {
	return f.js + `
(function() {
	document.getElementById("close").onclick = function(){
		open(' ', '_self').close();
	}
})();`
}
*/

type Button struct {
	ID      string
	Text    string
	OnClick func()
	sender  chan EventMsg
}

func (b *Button) Init(sender chan EventMsg) Controls {
	static := ConStatic{
		Name: "Button",
		CSS: `
.button {
    border: 4px solid #304ffe;
    color: white;
    background: #304ffe;
    padding: 6px 12px;
}
.button:hover {
    background: white;
    color: #304ffe;
}
.button:active {
    color: #fff;
    background: #304ffe;
    box-shadow: 1px 2px 7px rgba(0, 0, 0, 0.3) inset;
}`,
		JavaScript: `
function ButtonSetText(id,text) {
	var button = document.getElementById(id);
	button.textContent = text;
}
(function() {
	var buttons = document.getElementsByClassName('button');
	for (var i = 0; i < buttons.length; i++) {
		var button = buttons[i]
		button.onclick = function(){
			send(button.id, "ButtonOnClick", "");
		};
	}
})();`,
	}

	if b.ID == "" {
		b.ID = NewControlID()
	}
	events := make(map[string]func(v string))
	b.sender = sender

	html := "<button id=\"" + b.ID + "\"class=\"button\">" + b.Text + "</button>"
	if b.OnClick != nil {
		// 如果用户使用了OnClick事件
		// 那么添加事件
		events["ButtonOnClick"] = func(v string) {
			b.OnClick()
		}
	}
	return Controls{
		b.ID: Control{
			V:      b,
			HTML:   html,
			Static: static,
			Events: events,
		},
	}
}

// 设置按钮文字
func (b *Button) SetText(text string) {
	// 这里的判断是防止控件还没有初始化
	// sender还未赋值用户就调用
	if b.sender != nil {
		// ButtonSetText 为需要调用的js函数
		b.sender <- EventMsg{b.ID, "ButtonSetText", text}
	}
	b.Text = text
}

type Controls map[string]Control

type Control struct {
	V      interface{}               // 控件的结构体指针，用于初始化时的处理
	HTML   string                    // 控件的HTML代码
	Events map[string]func(v string) // 控件事件列表，key为事件名称
	Static ConStatic                 // 控件的静态数据，css和js等
}

type ConStatic struct {
	Name       string // 控件名称，不能和其他控件重复
	CSS        string // 控件的css
	JavaScript string // 控件的js
}

type EventMsg struct {
	ID    string `json:"id"`    // 控件的ID
	Event string `json:"event"` // 事件名称
	Value string `json:"value"` // 想发送信息的内容，复杂内容推荐用json编码
}

type NWUI struct {
	Name   string     `json:"name"`
	Main   string     `json:"main"`
	Window NWUIWindow `json:"window"`
}

type NWUIWindow struct {
	Show      bool   `json:"show"`
	Toolbar   bool   `json:"toolbar"`
	Position  string `json:"position"`
	Width     int    `json:"width"`
	Height    int    `json:"height"`
	MinWidth  int    `json:min_width`
	MinHeight int    `json:"min_height"`
	MaxWidth  int    `json:max_width`
	MaxHeight int    `json:"max_height"`
}
<commit_msg>修复未设置事件的控件触发事件导致报错<commit_after>/*
 Copyright 2015 Bluek404
 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 nwui

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"time"

	"github.com/gorilla/websocket"
)

var (
	// 用于生成随机数
	r    = rand.New(rand.NewSource(time.Now().UnixNano()))
	cons = make(map[string]interface{})
	temp = `<!DOCTYPE html>

<meta charset="utf-8">
<title>%v</title>

%v

`
)

func printInfo(v ...interface{}) {
	log.Println(append([]interface{}{"[nwui][Info]"}, v...)...)
}

func printError(v ...interface{}) {
	log.Println(append([]interface{}{"[nwui][Error]"}, v...)...)
}

// 生成控件ID
func NewControlID() string { return "_" + strconv.FormatInt(r.Int63(), 36) }

func GetConByID(id string) interface{} { return cons[id] }

// 显示窗口
// 必须在全部控件设置完毕后才能调用
func Show(window interface{}) {
	// 查找可用端口
	var port string
	for i := 7072; i <= 65536; i++ {
		p := strconv.Itoa(i)
		ln, err := net.Listen("tcp", "localhost:"+p)
		if err != nil {
			continue
		} else {
			port = p
			ln.Close()
			break
		}
	}
	if port == "" {
		printError("no port can use")
		os.Exit(1)
	}

	var (
		html     string
		css      string
		js       string
		events   = make(map[string]func(v string))
		upgrader = websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
		}
		sender = make(chan EventMsg)
		x      = make(map[string]bool)
	)

	vv := reflect.ValueOf(window).MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(sender)})

	vvv := vv[0].Interface().(Controls)
	for id, con := range vvv {
		if _, ok := x[con.Static.Name]; !ok {
			css += con.Static.CSS
			js += con.Static.JavaScript
			x[con.Static.Name] = true
		}
		cons[id] = con.V
		html += con.HTML
		for event, f := range con.Events {
			events[id+event] = f
		}
	}

	nwui := vv[1].Interface().(NWUI)
	exit := vv[2].Interface().(chan bool)

	http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(rw, temp, nwui.Name, css, html, "localhost:"+port, js)
		r.Body.Close()
	})
	http.HandleFunc("/ws", func(rw http.ResponseWriter, r *http.Request) {
		conn, err := upgrader.Upgrade(rw, r, nil)
		if err != nil && err != io.EOF {
			printError(err)
			return
		}

		// 用于接收事件
		go func() {
			for {
				messageType, p, err := conn.ReadMessage()
				if err != nil {
					printError(err)
					return
				}

				if messageType == websocket.TextMessage {
					var msg EventMsg
					err = json.Unmarshal(p, &msg)
					if err != nil {
						printError(err)
						return
					}

					// 执行事件所绑定的函数
					f, ok := events[msg.ID+msg.Event]
					if ok {
						f(msg.Value)
					}
				}
			}
		}()

		for {
			// 发送消息给前端
			m := <-sender
			msg, err := json.Marshal(&m)
			if err != nil {
				printError(err)
				return
			}
			err = conn.WriteMessage(websocket.TextMessage, msg)
			if err != nil {
				printError(err)
			}
		}
	})

	go func() {
		printInfo("running on localhost:" + port)
		err := http.ListenAndServe("localhost:"+port, nil)
		if err != nil {
			panic(err)
		}
	}()

	<-exit
}

// TODO: 移植自定义窗口控件
// TODO: 分离nwui框架和控件
// TODO: 更多控件+主题
// TODO: 自动启动nwjs

// nwui窗口
type Window struct {
	Title     string
	Width     int
	Height    int
	MaxWidth  int
	MaxHeight int
	MinWidth  int
	MinHeight int
	Controls  []interface{}
	OnExit    func()
	exit      chan bool
}

func (w *Window) Init(sender chan EventMsg) (Controls, NWUI, chan bool) {
	w.exit = make(chan bool)

	var (
		id   = NewControlID()
		cons = Controls{
			id: Control{
				Events: map[string]func(v string){"exit": func(v string) {
					if w.OnExit != nil {
						w.OnExit()
					}
					w.exit <- true
					return
				}},
				Static: ConStatic{
					Name: "Window",
					JavaScript: `
window.onunload = function() {
	send("` + id + `", "exit", "");
}`,
				},
			},
		}
		x = make(map[string]bool)
	)

	// 初始化控件
	for _, v := range w.Controls {
		// 返回html string, javascript string, events map[string]func(v string)
		vv := reflect.ValueOf(v).MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(sender)})

		vvv := vv[0].Interface().(Controls)

		for id, con := range vvv {
			// 检查ID是否重复
			if _, ok := x[id]; ok {
				panic("duplicate id: " + id)
			}

			cons[id] = con
		}
	}

	return cons, NWUI{
		Name: w.Title,
	}, w.exit
}

/*
defaultTheme = `
.frame {
    position: absolute;
    left: 0px;
    top: 0px;
    width: 100%;
    height: 32px;
    background-color: #424242;
    -webkit-app-region: drag;
}
.frame .title {
    color: white;
    position: absolute;
    left: 12px;
    width: 80%;
    margin-top: 6px;
    margin-bottom: 6px;
    font-size: 11pt;
}
.frame button#close {
    position: absolute;
    left: auto;
    right: 12px;
    width: auto;
    font-size: 11pt;
    -webkit-app-region: no-drag;
}
.main {
    margin-top: 40px;
}`
// 创建一个新的自定义窗口边框
// 一个 Window 中只能有一个 Frame
func NewFrame(title string, con ...Control) Frame {
	return &frame{
		title:    title,
		controls: con,
		events:   make(map[string]func(v string)),
	}
}
// 窗口边框
type Frame interface {
	Control
}
type frame struct {
	title    string
	js       string
	controls []Control
	events   map[string]func(v string)
	send     func(f, v string)
}
func (f *frame) getEvents() map[string]func(v string) {
	return f.events
}
func (f *frame) setSendFunc(fc func(f, v string)) {
	f.send = fc
}
func (f *frame) genHTML() string {
	// 转换内部控件
	var html string
	for _, v := range f.controls {
		v.setSendFunc(f.send)
		html += v.genHTML()
		f.js += v.genJavaScript()
		for e, fc := range v.getEvents() {
			f.events[e] = fc
		}
	}
	return `

` + f.title + `
<button id="close">x</button>

` + html + `

`
}
func (f *frame) genJavaScript() string {
	return f.js + `
(function() {
	document.getElementById("close").onclick = function(){
		open(' ', '_self').close();
	}
})();`
}
*/

type Button struct {
	ID      string
	Text    string
	OnClick func()
	sender  chan EventMsg
}

func (b *Button) Init(sender chan EventMsg) Controls {
	static := ConStatic{
		Name: "Button",
		CSS: `
.button {
    border: 4px solid #304ffe;
    color: white;
    background: #304ffe;
    padding: 6px 12px;
}
.button:hover {
    background: white;
    color: #304ffe;
}
.button:active {
    color: #fff;
    background: #304ffe;
    box-shadow: 1px 2px 7px rgba(0, 0, 0, 0.3) inset;
}`,
		JavaScript: `
function ButtonSetText(id,text) {
	var button = document.getElementById(id);
	button.textContent = text;
}
(function() {
	var buttons = document.getElementsByClassName('button');
	for (var i = 0; i < buttons.length; i++) {
		var button = buttons[i]
		button.onclick = function(){
			send(button.id, "ButtonOnClick", "");
		};
	}
})();`,
	}

	if b.ID == "" {
		b.ID = NewControlID()
	}
	events := make(map[string]func(v string))
	b.sender = sender

	html := "<button id=\"" + b.ID + "\"class=\"button\">" + b.Text + "</button>"
	if b.OnClick != nil {
		// 如果用户使用了OnClick事件
		// 那么添加事件
		events["ButtonOnClick"] = func(v string) {
			b.OnClick()
		}
	}
	return Controls{
		b.ID: Control{
			V:      b,
			HTML:   html,
			Static: static,
			Events: events,
		},
	}
}

// 设置按钮文字
func (b *Button) SetText(text string) {
	// 这里的判断是防止控件还没有初始化
	// sender还未赋值用户就调用
	if b.sender != nil {
		// ButtonSetText 为需要调用的js函数
		b.sender <- EventMsg{b.ID, "ButtonSetText", text}
	}
	b.Text = text
}

type Controls map[string]Control

type Control struct {
	V      interface{}               // 控件的结构体指针，用于初始化时的处理
	HTML   string                    // 控件的HTML代码
	Events map[string]func(v string) // 控件事件列表，key为事件名称
	Static ConStatic                 // 控件的静态数据，css和js等
}

type ConStatic struct {
	Name       string // 控件名称，不能和其他控件重复
	CSS        string // 控件的css
	JavaScript string // 控件的js
}

type EventMsg struct {
	ID    string `json:"id"`    // 控件的ID
	Event string `json:"event"` // 事件名称
	Value string `json:"value"` // 想发送信息的内容，复杂内容推荐用json编码
}

type NWUI struct {
	Name   string     `json:"name"`
	Main   string     `json:"main"`
	Window NWUIWindow `json:"window"`
}

type NWUIWindow struct {
	Show      bool   `json:"show"`
	Toolbar   bool   `json:"toolbar"`
	Position  string `json:"position"`
	Width     int    `json:"width"`
	Height    int    `json:"height"`
	MinWidth  int    `json:min_width`
	MinHeight int    `json:"min_height"`
	MaxWidth  int    `json:max_width`
	MaxHeight int    `json:"max_height"`
}
<|endoftext|>


<commit_before>// Package bale provides wrappers around go logging libraries, allowing users of the
// library to easily swap between logging implementations without changing client code.
package bale

// Logger defines the minimum set of methods implemented by all logging implementations.
type Logger interface {
	logPrinter
	logFormatter
}

// Log functions that handle arguments like fmt.Print.
type logPrinter interface {
	// Info logs to the INFO log.
	Info(args ...interface{})

	// Warning logs to the WARNING and INFO logs.
	Warn(args ...interface{})

	// Error logs to the ERROR, WARNING, and INFO logs.
	Error(args ...interface{})

	// Fatal logs to the FATAL, ERROR, WARNING, and INFO logs, and is expected
	// to exit the program.
	Fatal(args ...interface{})
}

// Log functions that handle arguments like fmt.Printf.
type logFormatter interface {
	// Infof logs to the INFO log.
	Infof(format string, args ...interface{})

	// Warningf logs to the WARNING and INFO logs.
	Warnf(format string, args ...interface{})

	// Errorf logs to the ERROR, WARNING, and INFO logs.
	Errorf(format string, args ...interface{})

	// Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs, and is expected
	// to exit the program.
	Fatalf(format string, args ...interface{})
}

// Log functions that handle arguments like fmt.Println.
type Logliner interface {
	// Infoln logs to the INFO log.
	Infoln(args ...interface{})

	// Warningln logs to the WARNING and INFO logs.
	Warnln(args ...interface{})

	// Errorln logs to the ERROR, WARNING, and INFO logs.
	Errorln(args ...interface{})

	// Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs, and is expected
	// to exit the program.
	Fatalln(args ...interface{})
}
<commit_msg>Copyright and License.<commit_after>// Copyright 2013 Cameron Walters. All Right 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 bale provides wrappers around go logging libraries, allowing users of the
// library to easily swap between logging implementations without changing client code.
package bale

// Logger defines the minimum set of methods implemented by all logging implementations.
type Logger interface {
	logPrinter
	logFormatter
}

// Log functions that handle arguments like fmt.Print.
type logPrinter interface {
	// Info logs to the INFO log.
	Info(args ...interface{})

	// Warning logs to the WARNING and INFO logs.
	Warn(args ...interface{})

	// Error logs to the ERROR, WARNING, and INFO logs.
	Error(args ...interface{})

	// Fatal logs to the FATAL, ERROR, WARNING, and INFO logs, and is expected
	// to exit the program.
	Fatal(args ...interface{})
}

// Log functions that handle arguments like fmt.Printf.
type logFormatter interface {
	// Infof logs to the INFO log.
	Infof(format string, args ...interface{})

	// Warningf logs to the WARNING and INFO logs.
	Warnf(format string, args ...interface{})

	// Errorf logs to the ERROR, WARNING, and INFO logs.
	Errorf(format string, args ...interface{})

	// Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs, and is expected
	// to exit the program.
	Fatalf(format string, args ...interface{})
}

// Log functions that handle arguments like fmt.Println.
type Logliner interface {
	// Infoln logs to the INFO log.
	Infoln(args ...interface{})

	// Warningln logs to the WARNING and INFO logs.
	Warnln(args ...interface{})

	// Errorln logs to the ERROR, WARNING, and INFO logs.
	Errorln(args ...interface{})

	// Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs, and is expected
	// to exit the program.
	Fatalln(args ...interface{})
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/aktau/sndcld/snd"
	"github.com/cheggaaa/pb"
)

var (
	help = flag.Bool("help", false, "print help")
)

func init() {
	flag.Parse()
}

func usage() {
	fmt.Println("Usage: sndcld [-v] <url-of-track>")
	os.Exit(0)
}

func main() {
	if *help {
		usage()
	}

	var soundUrl string
	if flag.NArg() < 1 {
		// usage()
		// for debugging, we assign a predefined sound
		soundUrl = "https://soundcloud.com/moullinex/discobelle-mix-041-moullinex"
	} else {
		soundUrl = flag.Arg(0)
	}

	fmt.Println("Welcome to sndcld. There is no sndcld though, only Zuul.")

	client := snd.Client{Id: "22e566527758690e6feb2b5cb300cc43"}

	url, err := client.Resolve(soundUrl)
	if err != nil {
		fmt.Printf("couldn't resolve sound %s: %v\n", soundUrl, err)
		os.Exit(1)
	}
	fmt.Println("got:", url, err)

	sound, err := client.GetSound(url)
	if err != nil {
		fmt.Printf("couldn't fetch sound %s (resolved: %s) metadata: %v\n",
			soundUrl, url, err)
		os.Exit(1)
	}
	fmt.Printf("got sound %s -> %s, %+v\n", soundUrl, url, sound)

	resp, err := client.DownloadSound(sound)
	if err != nil {
		fmt.Println("couldn't download sound:", err)
		os.Exit(1)
	}
	defer resp.Body.Close()

	fname, err := storeSound(sound, resp)
	if err != nil {
		fmt.Println("couldn't store sound locally:", err)
		os.Exit(1)
	}

	if err := sound.CompleteTags(fname); err != nil {
		fmt.Println("couldn't tag file:", err)
		os.Exit(1)
	}
}

func storeSound(sound *snd.Sound, resp *http.Response) (string, error) {
	h := resp.Header
	_, ext, err := snd.ParseFiletype(h.Get("Content-Type"), h.Get("Content-Disposition"))
	if err != nil {
		// if there's still nothing, assume it's mp3
		ext = ".mp3"
	}

	fname := sound.Filename() + ext

	f, err := os.Create(fname)
	if err != nil {
		return "", err
	}
	defer f.Close()

	// create progressbar
	nbytes, _ := strconv.Atoi(h.Get("Content-Length"))

	bar := pb.New(nbytes).SetUnits(pb.U_BYTES).SetRefreshRate(time.Millisecond * 10)
	bar.ShowSpeed = true
	bar.Start()
	defer bar.Finish()

	// create multi writer, write to the progress bar and the file at the
	// same time.
	writer := io.MultiWriter(f, bar)

	// stream the http response to the file (check if chunked encoding
	// doesn't mess with this)
	_, err = io.Copy(writer, resp.Body)
	return fname, err
}
<commit_msg>app: support tagging only<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/aktau/sndcld/snd"
	"github.com/cheggaaa/pb"
)

var (
	help = flag.Bool("help", false, "print help")
	tag  = flag.String("tag", "", "tag a file with the information of <url>")
)

func init() {
	flag.Parse()
}

func usage() {
	fmt.Println("Usage: sndcld [-v] <url-of-track>")
	fmt.Println("       sndcld -tag <filename-of-track> <url-of-track>")
	os.Exit(0)
}

func main() {
	if *help {
		usage()
	}

	var soundUrl string
	if flag.NArg() < 1 {
		// usage()
		// for debugging, we assign a predefined sound
		soundUrl = "https://soundcloud.com/moullinex/discobelle-mix-041-moullinex"
	} else {
		soundUrl = flag.Arg(0)
	}

	fmt.Println("Welcome to sndcld. There is no sndcld though, only Zuul.")

	client := snd.Client{Id: "22e566527758690e6feb2b5cb300cc43"}

	url, err := client.Resolve(soundUrl)
	if err != nil {
		fmt.Printf("couldn't resolve sound %s: %v\n", soundUrl, err)
		os.Exit(1)
	}
	fmt.Println("got:", url, err)

	sound, err := client.GetSound(url)
	if err != nil {
		fmt.Printf("couldn't fetch sound %s (resolved: %s) metadata: %v\n",
			soundUrl, url, err)
		os.Exit(1)
	}
	fmt.Printf("got sound %s -> %s, %+v\n", soundUrl, url, sound)

	if *tag != "" {
		// just tag the file, assume it's already downloaded
		tagfile(&client, sound, *tag)
	} else {
		// download and tag
		fname := download(&client, sound)
		tagfile(&client, sound, fname)
	}

}

func tagfile(client *snd.Client, sound *snd.Sound, fname string) {
	if err := sound.CompleteTags(fname); err != nil {
		fmt.Println("couldn't tag file:", err)
		os.Exit(1)
	}
}

func download(client *snd.Client, sound *snd.Sound) string {
	resp, err := client.DownloadSound(sound)
	if err != nil {
		fmt.Println("couldn't download sound:", err)
		os.Exit(1)
	}
	defer resp.Body.Close()

	fname, err := storeSound(sound, resp)
	if err != nil {
		fmt.Println("couldn't store sound locally:", err)
		os.Exit(1)
	}

	return fname
}

func storeSound(sound *snd.Sound, resp *http.Response) (string, error) {
	h := resp.Header
	_, ext, err := snd.ParseFiletype(h.Get("Content-Type"), h.Get("Content-Disposition"))
	if err != nil {
		// if there's still nothing, assume it's mp3
		ext = ".mp3"
	}

	fname := sound.Filename() + ext

	f, err := os.Create(fname)
	if err != nil {
		return "", err
	}
	defer f.Close()

	// create progressbar
	nbytes, _ := strconv.Atoi(h.Get("Content-Length"))

	bar := pb.New(nbytes).SetUnits(pb.U_BYTES).SetRefreshRate(time.Millisecond * 10)
	bar.ShowSpeed = true
	bar.Start()
	defer bar.Finish()

	// create multi writer, write to the progress bar and the file at the
	// same time.
	writer := io.MultiWriter(f, bar)

	// stream the http response to the file (check if chunked encoding
	// doesn't mess with this)
	_, err = io.Copy(writer, resp.Body)
	return fname, err
}
<|endoftext|>


<commit_before>package grpcclient

import (
	"context"
	"encoding/json"
	"io"
	"net"
	"os"
	"strings"
	"time"

	"github.com/gogo/googleapis/google/rpc"
	"github.com/moby/buildkit/frontend/gateway/client"
	pb "github.com/moby/buildkit/frontend/gateway/pb"
	opspb "github.com/moby/buildkit/solver/pb"
	"github.com/moby/buildkit/util/apicaps"
	digest "github.com/opencontainers/go-digest"
	specs "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"google.golang.org/grpc"
	"google.golang.org/grpc/status"
)

const frontendPrefix = "BUILDKIT_FRONTEND_OPT_"

func current() (*grpcClient, error) {
	if ep := product(); ep != "" {
		apicaps.ExportedProduct = ep
	}

	ctx, conn, err := grpcClientConn(context.Background())
	if err != nil {
		return nil, err
	}

	c := pb.NewLLBBridgeClient(conn)

	resp, err := c.Ping(ctx, &pb.PingRequest{})
	if err != nil {
		return nil, err
	}

	if resp.FrontendAPICaps == nil {
		resp.FrontendAPICaps = defaultCaps()
	}

	return &grpcClient{
		client:    c,
		opts:      opts(),
		sessionID: sessionID(),
		workers:   workers(),
		product:   product(),
		caps:      pb.Caps.CapSet(resp.FrontendAPICaps),
		requests:  map[string]*pb.SolveRequest{},
	}, nil
}

func convertRef(ref client.Reference) (string, error) {
	if ref == nil {
		return "", nil
	}
	r, ok := ref.(*reference)
	if !ok {
		return "", errors.Errorf("invalid return reference type %T", ref)
	}
	return r.id, nil
}

func Run(ctx context.Context, f client.BuildFunc) (retError error) {
	client, err := current()
	if err != nil {
		return errors.Wrapf(err, "failed to initialize client from environment")
	}

	res, err := f(ctx, client)

	export := client.caps.Supports(pb.CapReturnResult) == nil

	if export {
		defer func() {
			req := &pb.ReturnRequest{}
			if retError == nil {
				pbRes := &pb.Result{
					Metadata: res.Metadata,
				}
				if res.Refs != nil {
					m := map[string]string{}
					for k, r := range res.Refs {
						id, err := convertRef(r)
						if err != nil {
							retError = err
							continue
						}
						m[k] = id
					}
					pbRes.Result = &pb.Result_Refs{Refs: &pb.RefMap{Refs: m}}
				} else {
					id, err := convertRef(res.Ref)
					if err != nil {
						retError = err
					} else {
						pbRes.Result = &pb.Result_Ref{Ref: id}
					}
				}
				if retError == nil {
					req.Result = pbRes
				}
			}
			if retError != nil {
				st, _ := status.FromError(retError)
				stp := st.Proto()
				req.Error = &rpc.Status{
					Code:    stp.Code,
					Message: stp.Message,
					// Details: stp.Details,
				}
			}
			if _, err := client.client.Return(ctx, req); err != nil && retError == nil {
				retError = err
			}
		}()
	}

	if err != nil {
		return err
	}

	if err := client.caps.Supports(pb.CapReturnMap); len(res.Refs) > 1 && err != nil {
		return err
	}

	if !export {
		exportedAttrBytes, err := json.Marshal(res.Metadata)
		if err != nil {
			return errors.Wrapf(err, "failed to marshal return metadata")
		}

		req, err := client.requestForRef(res.Ref)
		if err != nil {
			return errors.Wrapf(err, "failed to find return ref")
		}

		req.Final = true
		req.ExporterAttr = exportedAttrBytes

		if _, err := client.client.Solve(ctx, req); err != nil {
			return errors.Wrapf(err, "failed to solve")
		}
	}

	return nil
}

// defaultCaps returns the capabilities that were implemented when capabilities
// support was added. This list is frozen and should never be changed.
func defaultCaps() []apicaps.PBCap {
	return []apicaps.PBCap{
		{ID: string(pb.CapSolveBase), Enabled: true},
		{ID: string(pb.CapSolveInlineReturn), Enabled: true},
		{ID: string(pb.CapResolveImage), Enabled: true},
		{ID: string(pb.CapReadFile), Enabled: true},
	}
}

type grpcClient struct {
	client    pb.LLBBridgeClient
	opts      map[string]string
	sessionID string
	product   string
	workers   []client.WorkerInfo
	caps      apicaps.CapSet
	requests  map[string]*pb.SolveRequest
}

func (c *grpcClient) requestForRef(ref client.Reference) (*pb.SolveRequest, error) {
	emptyReq := &pb.SolveRequest{
		Definition: &opspb.Definition{},
	}
	if ref == nil {
		return emptyReq, nil
	}
	r, ok := ref.(*reference)
	if !ok {
		return nil, errors.Errorf("return reference has invalid type %T", ref)
	}
	if r.id == "" {
		return emptyReq, nil
	}
	req, ok := c.requests[r.id]
	if !ok {
		return nil, errors.Errorf("did not find request for return reference %s", r.id)
	}
	return req, nil
}

func (c *grpcClient) Solve(ctx context.Context, creq client.SolveRequest) (*client.Result, error) {
	req := &pb.SolveRequest{
		Definition:        creq.Definition,
		Frontend:          creq.Frontend,
		FrontendOpt:       creq.FrontendOpt,
		ImportCacheRefs:   creq.ImportCacheRefs,
		AllowResultReturn: true,
	}

	// backwards compatibility with inline return
	if c.caps.Supports(pb.CapReturnResult) != nil {
		req.ExporterAttr = []byte("{}")
	}

	resp, err := c.client.Solve(ctx, req)
	if err != nil {
		return nil, err
	}

	res := &client.Result{}

	if resp.Result == nil {
		if id := resp.Ref; id != "" {
			c.requests[id] = req
		}
		res.SetRef(&reference{id: resp.Ref, c: c})
	} else {
		res.Metadata = resp.Result.Metadata
		switch pbRes := resp.Result.Result.(type) {
		case *pb.Result_Ref:
			if id := pbRes.Ref; id != "" {
				res.SetRef(&reference{id: id, c: c})
			}
		case *pb.Result_Refs:
			for k, v := range pbRes.Refs.Refs {
				ref := &reference{id: v, c: c}
				if v == "" {
					ref = nil
				}
				res.AddRef(k, ref)
			}
		}
	}

	return res, nil
}

func (c *grpcClient) ResolveImageConfig(ctx context.Context, ref string, platform *specs.Platform) (digest.Digest, []byte, error) {
	var p *opspb.Platform
	if platform != nil {
		p = &opspb.Platform{
			OS:           platform.OS,
			Architecture: platform.Architecture,
			Variant:      platform.Variant,
			OSVersion:    platform.OSVersion,
			OSFeatures:   platform.OSFeatures,
		}
	}
	resp, err := c.client.ResolveImageConfig(ctx, &pb.ResolveImageConfigRequest{Ref: ref, Platform: p})
	if err != nil {
		return "", nil, err
	}
	return resp.Digest, resp.Config, nil
}

func (c *grpcClient) BuildOpts() client.BuildOpts {
	return client.BuildOpts{
		Opts:      c.opts,
		SessionID: c.sessionID,
		Workers:   c.workers,
		Product:   c.product,
	}
}

func (c *grpcClient) Opts() map[string]string {
	return c.opts
}

func (c *grpcClient) SessionID() string {
	return c.sessionID
}

func (c *grpcClient) WorkerInfos() []client.WorkerInfo {
	return c.workers
}

type reference struct {
	id string
	c  *grpcClient
}

func (r *reference) ReadFile(ctx context.Context, req client.ReadRequest) ([]byte, error) {
	rfr := &pb.ReadFileRequest{FilePath: req.Filename, Ref: r.id}
	if r := req.Range; r != nil {
		rfr.Range = &pb.FileRange{
			Offset: int64(r.Offset),
			Length: int64(r.Length),
		}
	}
	resp, err := r.c.client.ReadFile(ctx, rfr)
	if err != nil {
		return nil, err
	}
	return resp.Data, nil
}

func grpcClientConn(ctx context.Context) (context.Context, *grpc.ClientConn, error) {
	dialOpt := grpc.WithDialer(func(addr string, d time.Duration) (net.Conn, error) {
		return stdioConn(), nil
	})

	cc, err := grpc.DialContext(ctx, "", dialOpt, grpc.WithInsecure())
	if err != nil {
		return nil, nil, errors.Wrap(err, "failed to create grpc client")
	}

	ctx, cancel := context.WithCancel(ctx)
	_ = cancel
	// go monitorHealth(ctx, cc, cancel)

	return ctx, cc, nil
}

func stdioConn() net.Conn {
	return &conn{os.Stdin, os.Stdout, os.Stdout}
}

type conn struct {
	io.Reader
	io.Writer
	io.Closer
}

func (s *conn) LocalAddr() net.Addr {
	return dummyAddr{}
}
func (s *conn) RemoteAddr() net.Addr {
	return dummyAddr{}
}
func (s *conn) SetDeadline(t time.Time) error {
	return nil
}
func (s *conn) SetReadDeadline(t time.Time) error {
	return nil
}
func (s *conn) SetWriteDeadline(t time.Time) error {
	return nil
}

type dummyAddr struct {
}

func (d dummyAddr) Network() string {
	return "pipe"
}

func (d dummyAddr) String() string {
	return "localhost"
}

func opts() map[string]string {
	opts := map[string]string{}
	for _, env := range os.Environ() {
		parts := strings.SplitN(env, "=", 2)
		k := parts[0]
		v := ""
		if len(parts) == 2 {
			v = parts[1]
		}
		if !strings.HasPrefix(k, frontendPrefix) {
			continue
		}
		parts = strings.SplitN(v, "=", 2)
		v = ""
		if len(parts) == 2 {
			v = parts[1]
		}
		opts[parts[0]] = v
	}
	return opts
}

func sessionID() string {
	return os.Getenv("BUILDKIT_SESSION_ID")
}

func workers() []client.WorkerInfo {
	var c []client.WorkerInfo
	if err := json.Unmarshal([]byte(os.Getenv("BUILDKIT_WORKERS")), &c); err != nil {
		return nil
	}
	return c
}

func product() string {
	return os.Getenv("BUILDKIT_EXPORTEDPRODUCT")
}
<commit_msg>gateway: validate LLB capabilities<commit_after>package grpcclient

import (
	"context"
	"encoding/json"
	"io"
	"net"
	"os"
	"strings"
	"time"

	"github.com/gogo/googleapis/google/rpc"
	"github.com/moby/buildkit/frontend/gateway/client"
	pb "github.com/moby/buildkit/frontend/gateway/pb"
	opspb "github.com/moby/buildkit/solver/pb"
	"github.com/moby/buildkit/util/apicaps"
	digest "github.com/opencontainers/go-digest"
	specs "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"google.golang.org/grpc"
	"google.golang.org/grpc/status"
)

const frontendPrefix = "BUILDKIT_FRONTEND_OPT_"

func current() (*grpcClient, error) {
	if ep := product(); ep != "" {
		apicaps.ExportedProduct = ep
	}

	ctx, conn, err := grpcClientConn(context.Background())
	if err != nil {
		return nil, err
	}

	c := pb.NewLLBBridgeClient(conn)

	resp, err := c.Ping(ctx, &pb.PingRequest{})
	if err != nil {
		return nil, err
	}

	if resp.FrontendAPICaps == nil {
		resp.FrontendAPICaps = defaultCaps()
	}

	if resp.LLBCaps == nil {
		resp.LLBCaps = defaultLLBCaps()
	}

	return &grpcClient{
		client:    c,
		opts:      opts(),
		sessionID: sessionID(),
		workers:   workers(),
		product:   product(),
		caps:      pb.Caps.CapSet(resp.FrontendAPICaps),
		llbCaps:   opspb.Caps.CapSet(resp.LLBCaps),
		requests:  map[string]*pb.SolveRequest{},
	}, nil
}

func convertRef(ref client.Reference) (string, error) {
	if ref == nil {
		return "", nil
	}
	r, ok := ref.(*reference)
	if !ok {
		return "", errors.Errorf("invalid return reference type %T", ref)
	}
	return r.id, nil
}

func Run(ctx context.Context, f client.BuildFunc) (retError error) {
	client, err := current()
	if err != nil {
		return errors.Wrapf(err, "failed to initialize client from environment")
	}

	res, err := f(ctx, client)

	export := client.caps.Supports(pb.CapReturnResult) == nil

	if export {
		defer func() {
			req := &pb.ReturnRequest{}
			if retError == nil {
				pbRes := &pb.Result{
					Metadata: res.Metadata,
				}
				if res.Refs != nil {
					m := map[string]string{}
					for k, r := range res.Refs {
						id, err := convertRef(r)
						if err != nil {
							retError = err
							continue
						}
						m[k] = id
					}
					pbRes.Result = &pb.Result_Refs{Refs: &pb.RefMap{Refs: m}}
				} else {
					id, err := convertRef(res.Ref)
					if err != nil {
						retError = err
					} else {
						pbRes.Result = &pb.Result_Ref{Ref: id}
					}
				}
				if retError == nil {
					req.Result = pbRes
				}
			}
			if retError != nil {
				st, _ := status.FromError(retError)
				stp := st.Proto()
				req.Error = &rpc.Status{
					Code:    stp.Code,
					Message: stp.Message,
					// Details: stp.Details,
				}
			}
			if _, err := client.client.Return(ctx, req); err != nil && retError == nil {
				retError = err
			}
		}()
	}

	if err != nil {
		return err
	}

	if err := client.caps.Supports(pb.CapReturnMap); len(res.Refs) > 1 && err != nil {
		return err
	}

	if !export {
		exportedAttrBytes, err := json.Marshal(res.Metadata)
		if err != nil {
			return errors.Wrapf(err, "failed to marshal return metadata")
		}

		req, err := client.requestForRef(res.Ref)
		if err != nil {
			return errors.Wrapf(err, "failed to find return ref")
		}

		req.Final = true
		req.ExporterAttr = exportedAttrBytes

		if _, err := client.client.Solve(ctx, req); err != nil {
			return errors.Wrapf(err, "failed to solve")
		}
	}

	return nil
}

// defaultCaps returns the capabilities that were implemented when capabilities
// support was added. This list is frozen and should never be changed.
func defaultCaps() []apicaps.PBCap {
	return []apicaps.PBCap{
		{ID: string(pb.CapSolveBase), Enabled: true},
		{ID: string(pb.CapSolveInlineReturn), Enabled: true},
		{ID: string(pb.CapResolveImage), Enabled: true},
		{ID: string(pb.CapReadFile), Enabled: true},
	}
}

// defaultLLBCaps returns the LLB capabilities that were implemented when capabilities
// support was added. This list is frozen and should never be changed.
func defaultLLBCaps() []apicaps.PBCap {
	return []apicaps.PBCap{
		{ID: string(opspb.CapSourceImage), Enabled: true},
		{ID: string(opspb.CapSourceLocal), Enabled: true},
		{ID: string(opspb.CapSourceLocalUnique), Enabled: true},
		{ID: string(opspb.CapSourceLocalSessionID), Enabled: true},
		{ID: string(opspb.CapSourceLocalIncludePatterns), Enabled: true},
		{ID: string(opspb.CapSourceLocalFollowPaths), Enabled: true},
		{ID: string(opspb.CapSourceLocalExcludePatterns), Enabled: true},
		{ID: string(opspb.CapSourceLocalSharedKeyHint), Enabled: true},
		{ID: string(opspb.CapSourceGit), Enabled: true},
		{ID: string(opspb.CapSourceGitKeepDir), Enabled: true},
		{ID: string(opspb.CapSourceGitFullURL), Enabled: true},
		{ID: string(opspb.CapSourceHTTP), Enabled: true},
		{ID: string(opspb.CapSourceHTTPChecksum), Enabled: true},
		{ID: string(opspb.CapSourceHTTPPerm), Enabled: true},
		{ID: string(opspb.CapSourceHTTPUIDGID), Enabled: true},
		{ID: string(opspb.CapBuildOpLLBFileName), Enabled: true},
		{ID: string(opspb.CapExecMetaBase), Enabled: true},
		{ID: string(opspb.CapExecMetaProxy), Enabled: true},
		{ID: string(opspb.CapExecMountBind), Enabled: true},
		{ID: string(opspb.CapExecMountCache), Enabled: true},
		{ID: string(opspb.CapExecMountCacheSharing), Enabled: true},
		{ID: string(opspb.CapExecMountSelector), Enabled: true},
		{ID: string(opspb.CapExecMountTmpfs), Enabled: true},
		{ID: string(opspb.CapMountSecret), Enabled: true},
		{ID: string(opspb.CapConstraints), Enabled: true},
		{ID: string(opspb.CapPlatform), Enabled: true},
		{ID: string(opspb.CapMetaIgnoreCache), Enabled: true},
		{ID: string(opspb.CapMetaDescription), Enabled: true},
		{ID: string(opspb.CapMetaExportCache), Enabled: true},
	}
}

type grpcClient struct {
	client    pb.LLBBridgeClient
	opts      map[string]string
	sessionID string
	product   string
	workers   []client.WorkerInfo
	caps      apicaps.CapSet
	llbCaps   apicaps.CapSet
	requests  map[string]*pb.SolveRequest
}

func (c *grpcClient) requestForRef(ref client.Reference) (*pb.SolveRequest, error) {
	emptyReq := &pb.SolveRequest{
		Definition: &opspb.Definition{},
	}
	if ref == nil {
		return emptyReq, nil
	}
	r, ok := ref.(*reference)
	if !ok {
		return nil, errors.Errorf("return reference has invalid type %T", ref)
	}
	if r.id == "" {
		return emptyReq, nil
	}
	req, ok := c.requests[r.id]
	if !ok {
		return nil, errors.Errorf("did not find request for return reference %s", r.id)
	}
	return req, nil
}

func (c *grpcClient) Solve(ctx context.Context, creq client.SolveRequest) (*client.Result, error) {
	for _, md := range creq.Definition.Metadata {
		for cap := range md.Caps {
			if err := c.llbCaps.Supports(cap); err != nil {
				return nil, err
			}
		}
	}

	req := &pb.SolveRequest{
		Definition:        creq.Definition,
		Frontend:          creq.Frontend,
		FrontendOpt:       creq.FrontendOpt,
		ImportCacheRefs:   creq.ImportCacheRefs,
		AllowResultReturn: true,
	}

	// backwards compatibility with inline return
	if c.caps.Supports(pb.CapReturnResult) != nil {
		req.ExporterAttr = []byte("{}")
	}

	resp, err := c.client.Solve(ctx, req)
	if err != nil {
		return nil, err
	}

	res := &client.Result{}

	if resp.Result == nil {
		if id := resp.Ref; id != "" {
			c.requests[id] = req
		}
		res.SetRef(&reference{id: resp.Ref, c: c})
	} else {
		res.Metadata = resp.Result.Metadata
		switch pbRes := resp.Result.Result.(type) {
		case *pb.Result_Ref:
			if id := pbRes.Ref; id != "" {
				res.SetRef(&reference{id: id, c: c})
			}
		case *pb.Result_Refs:
			for k, v := range pbRes.Refs.Refs {
				ref := &reference{id: v, c: c}
				if v == "" {
					ref = nil
				}
				res.AddRef(k, ref)
			}
		}
	}

	return res, nil
}

func (c *grpcClient) ResolveImageConfig(ctx context.Context, ref string, platform *specs.Platform) (digest.Digest, []byte, error) {
	var p *opspb.Platform
	if platform != nil {
		p = &opspb.Platform{
			OS:           platform.OS,
			Architecture: platform.Architecture,
			Variant:      platform.Variant,
			OSVersion:    platform.OSVersion,
			OSFeatures:   platform.OSFeatures,
		}
	}
	resp, err := c.client.ResolveImageConfig(ctx, &pb.ResolveImageConfigRequest{Ref: ref, Platform: p})
	if err != nil {
		return "", nil, err
	}
	return resp.Digest, resp.Config, nil
}

func (c *grpcClient) BuildOpts() client.BuildOpts {
	return client.BuildOpts{
		Opts:      c.opts,
		SessionID: c.sessionID,
		Workers:   c.workers,
		Product:   c.product,
	}
}

func (c *grpcClient) Opts() map[string]string {
	return c.opts
}

func (c *grpcClient) SessionID() string {
	return c.sessionID
}

func (c *grpcClient) WorkerInfos() []client.WorkerInfo {
	return c.workers
}

type reference struct {
	id string
	c  *grpcClient
}

func (r *reference) ReadFile(ctx context.Context, req client.ReadRequest) ([]byte, error) {
	rfr := &pb.ReadFileRequest{FilePath: req.Filename, Ref: r.id}
	if r := req.Range; r != nil {
		rfr.Range = &pb.FileRange{
			Offset: int64(r.Offset),
			Length: int64(r.Length),
		}
	}
	resp, err := r.c.client.ReadFile(ctx, rfr)
	if err != nil {
		return nil, err
	}
	return resp.Data, nil
}

func grpcClientConn(ctx context.Context) (context.Context, *grpc.ClientConn, error) {
	dialOpt := grpc.WithDialer(func(addr string, d time.Duration) (net.Conn, error) {
		return stdioConn(), nil
	})

	cc, err := grpc.DialContext(ctx, "", dialOpt, grpc.WithInsecure())
	if err != nil {
		return nil, nil, errors.Wrap(err, "failed to create grpc client")
	}

	ctx, cancel := context.WithCancel(ctx)
	_ = cancel
	// go monitorHealth(ctx, cc, cancel)

	return ctx, cc, nil
}

func stdioConn() net.Conn {
	return &conn{os.Stdin, os.Stdout, os.Stdout}
}

type conn struct {
	io.Reader
	io.Writer
	io.Closer
}

func (s *conn) LocalAddr() net.Addr {
	return dummyAddr{}
}
func (s *conn) RemoteAddr() net.Addr {
	return dummyAddr{}
}
func (s *conn) SetDeadline(t time.Time) error {
	return nil
}
func (s *conn) SetReadDeadline(t time.Time) error {
	return nil
}
func (s *conn) SetWriteDeadline(t time.Time) error {
	return nil
}

type dummyAddr struct {
}

func (d dummyAddr) Network() string {
	return "pipe"
}

func (d dummyAddr) String() string {
	return "localhost"
}

func opts() map[string]string {
	opts := map[string]string{}
	for _, env := range os.Environ() {
		parts := strings.SplitN(env, "=", 2)
		k := parts[0]
		v := ""
		if len(parts) == 2 {
			v = parts[1]
		}
		if !strings.HasPrefix(k, frontendPrefix) {
			continue
		}
		parts = strings.SplitN(v, "=", 2)
		v = ""
		if len(parts) == 2 {
			v = parts[1]
		}
		opts[parts[0]] = v
	}
	return opts
}

func sessionID() string {
	return os.Getenv("BUILDKIT_SESSION_ID")
}

func workers() []client.WorkerInfo {
	var c []client.WorkerInfo
	if err := json.Unmarshal([]byte(os.Getenv("BUILDKIT_WORKERS")), &c); err != nil {
		return nil
	}
	return c
}

func product() string {
	return os.Getenv("BUILDKIT_EXPORTEDPRODUCT")
}
<|endoftext|>


<commit_before>package slack

type BotAction func(self *Bot, event map[string]interface{}) (*Message, Status)
<commit_msg>add comment for BotAction type<commit_after>package slack

// BotAction represents a handler for an RTM event. A valid BotAction takes a
// reference to the bot, as well as the event that caused it to fire. It should
// then return a reference to a Message (which can be nil), and a Status, which
// instructs the bot's main loop in whether to continue listening and sending
// messages or to terminate.
type BotAction func(self *Bot, event map[string]interface{}) (*Message, Status)
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
)

func TestAccAWSDataElasticsearchDomain_basic(t *testing.T) {
	rInt := acctest.RandInt()
	datasourceName := "data.aws_elasticsearch_domain.test"
	resourceName := "aws_elasticsearch_domain.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSElasticsearchDomainConfigWithDataSource(rInt),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrPair(datasourceName, "elasticsearch_version", resourceName, "elasticsearch_version"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.#", resourceName, "cluster_config.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.instance_type", resourceName, "cluster_config.0.instance_type"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.instance_count", resourceName, "cluster_config.0.instance_count"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.dedicated_master_enabled", resourceName, "cluster_config.0.dedicated_master_enabled"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.zone_awareness_enabled", resourceName, "cluster_config.0.zone_awareness_enabled"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.#", resourceName, "ebs_options.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.0.ebs_enabled", resourceName, "ebs_options.0.ebs_enabled"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.0.volume_type", resourceName, "ebs_options.0.volume_type"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.0.volume_size", resourceName, "ebs_options.0.volume_size"),
					resource.TestCheckResourceAttrPair(datasourceName, "snapshot_options.#", resourceName, "snapshot_options.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "snapshot_options.0.automated_snapshot_start_hour", resourceName, "snapshot_options.0.automated_snapshot_start_hour"),
				),
			},
		},
	})
}

func TestAccAWSDataElasticsearchDomain_advanced(t *testing.T) {
	rInt := acctest.RandInt()
	datasourceName := "data.aws_elasticsearch_domain.test"
	resourceName := "aws_elasticsearch_domain.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSElasticsearchDomainConfigAdvancedWithDataSource(rInt),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrPair(datasourceName, "elasticsearch_version", resourceName, "elasticsearch_version"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.#", resourceName, "cluster_config.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.instance_type", resourceName, "cluster_config.0.instance_type"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.instance_count", resourceName, "cluster_config.0.instance_count"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.dedicated_master_enabled", resourceName, "cluster_config.0.dedicated_master_enabled"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.zone_awareness_enabled", resourceName, "cluster_config.0.zone_awareness_enabled"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.#", resourceName, "ebs_options.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.0.ebs_enabled", resourceName, "ebs_options.0.ebs_enabled"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.0.volume_type", resourceName, "ebs_options.0.volume_type"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.0.volume_size", resourceName, "ebs_options.0.volume_size"),
					resource.TestCheckResourceAttrPair(datasourceName, "snapshot_options.#", resourceName, "snapshot_options.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "snapshot_options.0.automated_snapshot_start_hour", resourceName, "snapshot_options.0.automated_snapshot_start_hour"),
					resource.TestCheckResourceAttrPair(datasourceName, "log_publishing_options.#", resourceName, "log_publishing_options.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "log_publishing_options.0.log_type", resourceName, "log_publishing_options.0.log_type"),
					resource.TestCheckResourceAttrPair(datasourceName, "vpc_options.#", resourceName, "vpc_options.#"),
				),
			},
		},
	})
}

func testAccAWSElasticsearchDomainConfigWithDataSource(rInt int) string {
	return fmt.Sprintf(`
provider "aws" {
	region = "us-east-1"
}

locals {
	random_name = "test-es-%d"
}

data "aws_region" "current" {}

data "aws_caller_identity" "current" {}

resource "aws_elasticsearch_domain" "test" {
	domain_name = "${local.random_name}"
	elasticsearch_version = "1.5"

	access_policies = <<POLICY
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Action": "es:*",
			"Principal": "*",
			"Effect": "Allow",
			"Resource": "arn:aws:es:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:domain/${local.random_name}/*",
			"Condition": {
				"IpAddress": {"aws:SourceIp": ["127.0.0.0/8"]}
			}
		}
	]
}
POLICY

  cluster_config {
    instance_type = "t2.micro.elasticsearch"
    instance_count = 2
	dedicated_master_enabled = false
	zone_awareness_config {
		availability_zone_count = 2
	}
    zone_awareness_enabled = true
  }
  ebs_options {
	ebs_enabled = true
	volume_type = "gp2"
	volume_size = 20
  }
  snapshot_options {
    automated_snapshot_start_hour = 23
  }
}

data "aws_elasticsearch_domain" "test" {
  domain_name = "${aws_elasticsearch_domain.test.domain_name}"
}
		`, rInt)
}

func testAccAWSElasticsearchDomainConfigAdvancedWithDataSource(rInt int) string {
	return fmt.Sprintf(`
provider "aws" {
	region = "us-east-1"
}

data "aws_region" "current" {}

data "aws_caller_identity" "current" {}

locals {
	random_name = "test-es-%d"
}

resource "aws_cloudwatch_log_group" "test" {
	name = "${local.random_name}"
}

resource "aws_cloudwatch_log_resource_policy" "test" {
	policy_name = "${local.random_name}"
	policy_document = <<CONFIG
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Principal": {
				"Service": "es.amazonaws.com"
			},
			"Action": [
				"logs:PutLogEvents",
				"logs:PutLogEventsBatch",
				"logs:CreateLogStream"
			],
			"Resource": "arn:aws:logs:*"
		}
	]
}
CONFIG
}

resource "aws_vpc" "test" {
	cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "test" {
	vpc_id = "${aws_vpc.test.id}"
	cidr_block = "10.0.0.0/24"
}

resource "aws_subnet" "test2" {
	vpc_id = "${aws_vpc.test.id}"
	cidr_block = "10.0.1.0/24"
}

resource "aws_security_group" "test" {
	name = "${local.random_name}"
	vpc_id = "${aws_vpc.test.id}"
}

resource "aws_security_group_rule" "test" {
	type = "ingress"
	from_port = 443
	to_port = 443
	protocol = "tcp"
	cidr_blocks = [ "0.0.0.0/0" ]

	security_group_id = "${aws_security_group.test.id}"
}

resource "aws_iam_service_linked_role" "test" {
	aws_service_name = "es.amazonaws.com"
}

resource "aws_elasticsearch_domain" "test" {
	domain_name = "${local.random_name}"
	elasticsearch_version = "1.5"

	access_policies = <<POLICY
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Action": "es:*",
			"Principal": "*",
			"Effect": "Allow",
			"Resource": "arn:aws:es:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:domain/${local.random_name}/*"
		}
	]
}
POLICY

  cluster_config {
    instance_type = "t2.micro.elasticsearch"
    instance_count = 2
    dedicated_master_enabled = false
	zone_awareness_config {
		availability_zone_count = 2
	}
    zone_awareness_enabled = true
  }
  ebs_options {
	ebs_enabled = true
	volume_type = "gp2"
	volume_size = 20
  }
  snapshot_options {
    automated_snapshot_start_hour = 23
  }
  log_publishing_options {
    cloudwatch_log_group_arn = "${aws_cloudwatch_log_group.test.arn}"
    log_type = "INDEX_SLOW_LOGS"
  }
  vpc_options {
		security_group_ids = [
			"${aws_security_group.test.id}"
		]
		subnet_ids = [
			"${aws_subnet.test.id}",
			"${aws_subnet.test2.id}"
		]
  }

  tags = {
	Domain = "TestDomain"
  }

  depends_on = [
    "aws_iam_service_linked_role.test",
  ]
}

data "aws_elasticsearch_domain" "test" {
  domain_name = "${aws_elasticsearch_domain.test.domain_name}"
}
				`, rInt)
}
<commit_msg>removed log_type check because there seems to be odd behaviour with the resource<commit_after>package aws

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
)

func TestAccAWSDataElasticsearchDomain_basic(t *testing.T) {
	rInt := acctest.RandInt()
	datasourceName := "data.aws_elasticsearch_domain.test"
	resourceName := "aws_elasticsearch_domain.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSElasticsearchDomainConfigWithDataSource(rInt),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrPair(datasourceName, "elasticsearch_version", resourceName, "elasticsearch_version"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.#", resourceName, "cluster_config.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.instance_type", resourceName, "cluster_config.0.instance_type"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.instance_count", resourceName, "cluster_config.0.instance_count"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.dedicated_master_enabled", resourceName, "cluster_config.0.dedicated_master_enabled"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.zone_awareness_enabled", resourceName, "cluster_config.0.zone_awareness_enabled"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.#", resourceName, "ebs_options.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.0.ebs_enabled", resourceName, "ebs_options.0.ebs_enabled"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.0.volume_type", resourceName, "ebs_options.0.volume_type"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.0.volume_size", resourceName, "ebs_options.0.volume_size"),
					resource.TestCheckResourceAttrPair(datasourceName, "snapshot_options.#", resourceName, "snapshot_options.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "snapshot_options.0.automated_snapshot_start_hour", resourceName, "snapshot_options.0.automated_snapshot_start_hour"),
				),
			},
		},
	})
}

func TestAccAWSDataElasticsearchDomain_advanced(t *testing.T) {
	rInt := acctest.RandInt()
	datasourceName := "data.aws_elasticsearch_domain.test"
	resourceName := "aws_elasticsearch_domain.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSElasticsearchDomainConfigAdvancedWithDataSource(rInt),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrPair(datasourceName, "elasticsearch_version", resourceName, "elasticsearch_version"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.#", resourceName, "cluster_config.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.instance_type", resourceName, "cluster_config.0.instance_type"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.instance_count", resourceName, "cluster_config.0.instance_count"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.dedicated_master_enabled", resourceName, "cluster_config.0.dedicated_master_enabled"),
					resource.TestCheckResourceAttrPair(datasourceName, "cluster_config.0.zone_awareness_enabled", resourceName, "cluster_config.0.zone_awareness_enabled"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.#", resourceName, "ebs_options.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.0.ebs_enabled", resourceName, "ebs_options.0.ebs_enabled"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.0.volume_type", resourceName, "ebs_options.0.volume_type"),
					resource.TestCheckResourceAttrPair(datasourceName, "ebs_options.0.volume_size", resourceName, "ebs_options.0.volume_size"),
					resource.TestCheckResourceAttrPair(datasourceName, "snapshot_options.#", resourceName, "snapshot_options.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "snapshot_options.0.automated_snapshot_start_hour", resourceName, "snapshot_options.0.automated_snapshot_start_hour"),
					resource.TestCheckResourceAttrPair(datasourceName, "log_publishing_options.#", resourceName, "log_publishing_options.#"),
					resource.TestCheckResourceAttrPair(datasourceName, "vpc_options.#", resourceName, "vpc_options.#"),
				),
			},
		},
	})
}

func testAccAWSElasticsearchDomainConfigWithDataSource(rInt int) string {
	return fmt.Sprintf(`
provider "aws" {
	region = "us-east-1"
}

locals {
	random_name = "test-es-%d"
}

data "aws_region" "current" {}

data "aws_caller_identity" "current" {}

resource "aws_elasticsearch_domain" "test" {
	domain_name = "${local.random_name}"
	elasticsearch_version = "1.5"

	access_policies = <<POLICY
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Action": "es:*",
			"Principal": "*",
			"Effect": "Allow",
			"Resource": "arn:aws:es:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:domain/${local.random_name}/*",
			"Condition": {
				"IpAddress": {"aws:SourceIp": ["127.0.0.0/8"]}
			}
		}
	]
}
POLICY

  cluster_config {
    instance_type = "t2.micro.elasticsearch"
    instance_count = 2
	dedicated_master_enabled = false
	zone_awareness_config {
		availability_zone_count = 2
	}
    zone_awareness_enabled = true
  }
  ebs_options {
	ebs_enabled = true
	volume_type = "gp2"
	volume_size = 20
  }
  snapshot_options {
    automated_snapshot_start_hour = 23
  }
}

data "aws_elasticsearch_domain" "test" {
  domain_name = "${aws_elasticsearch_domain.test.domain_name}"
}
		`, rInt)
}

func testAccAWSElasticsearchDomainConfigAdvancedWithDataSource(rInt int) string {
	return fmt.Sprintf(`
provider "aws" {
	region = "us-east-1"
}

data "aws_region" "current" {}

data "aws_caller_identity" "current" {}

locals {
	random_name = "test-es-%d"
}

resource "aws_cloudwatch_log_group" "test" {
	name = "${local.random_name}"
}

resource "aws_cloudwatch_log_resource_policy" "test" {
	policy_name = "${local.random_name}"
	policy_document = <<CONFIG
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Principal": {
				"Service": "es.amazonaws.com"
			},
			"Action": [
				"logs:PutLogEvents",
				"logs:PutLogEventsBatch",
				"logs:CreateLogStream"
			],
			"Resource": "arn:aws:logs:*"
		}
	]
}
CONFIG
}

resource "aws_vpc" "test" {
	cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "test" {
	vpc_id = "${aws_vpc.test.id}"
	cidr_block = "10.0.0.0/24"
}

resource "aws_subnet" "test2" {
	vpc_id = "${aws_vpc.test.id}"
	cidr_block = "10.0.1.0/24"
}

resource "aws_security_group" "test" {
	name = "${local.random_name}"
	vpc_id = "${aws_vpc.test.id}"
}

resource "aws_security_group_rule" "test" {
	type = "ingress"
	from_port = 443
	to_port = 443
	protocol = "tcp"
	cidr_blocks = [ "0.0.0.0/0" ]

	security_group_id = "${aws_security_group.test.id}"
}

resource "aws_iam_service_linked_role" "test" {
	aws_service_name = "es.amazonaws.com"
}

resource "aws_elasticsearch_domain" "test" {
	domain_name = "${local.random_name}"
	elasticsearch_version = "1.5"

	access_policies = <<POLICY
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Action": "es:*",
			"Principal": "*",
			"Effect": "Allow",
			"Resource": "arn:aws:es:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:domain/${local.random_name}/*"
		}
	]
}
POLICY

  cluster_config {
    instance_type = "t2.micro.elasticsearch"
    instance_count = 2
    dedicated_master_enabled = false
	zone_awareness_config {
		availability_zone_count = 2
	}
    zone_awareness_enabled = true
  }
  ebs_options {
	ebs_enabled = true
	volume_type = "gp2"
	volume_size = 20
  }
  snapshot_options {
    automated_snapshot_start_hour = 23
  }
  log_publishing_options {
    cloudwatch_log_group_arn = "${aws_cloudwatch_log_group.test.arn}"
    log_type = "INDEX_SLOW_LOGS"
  }
  vpc_options {
		security_group_ids = [
			"${aws_security_group.test.id}"
		]
		subnet_ids = [
			"${aws_subnet.test.id}",
			"${aws_subnet.test2.id}"
		]
  }

  tags = {
	Domain = "TestDomain"
  }

  depends_on = [
    "aws_iam_service_linked_role.test",
  ]
}

data "aws_elasticsearch_domain" "test" {
  domain_name = "${aws_elasticsearch_domain.test.domain_name}"
}
				`, rInt)
}
<|endoftext|>